2 * This program is licensed under the GNU General Public License,
3 * version 2. A copy of the license can be found in the accompanying
6 **********************************************************************
8 * Simple program to limit the cpu usage of a process
9 * If you modify this code, send me a copy please
11 * Author: Angelo Marletta
15 * Modifications and updates by: Jesse Smith
28 #include <sys/types.h>
30 #include <sys/resource.h>
35 #include <limits.h> // for compatibility
38 //kernel time resolution (inverse of one jiffy interval) in Hertz
39 //i don't know how to detect it, then define to the default (not very clean!)
43 #define min(a,b) (a<b?a:b)
44 #define max(a,b) (a>b?a:b)
46 //pid of the controlled process
48 //executable file name
54 // is higher priority nice possible?
58 void *memrchr(const void *s, int c, size_t n);
60 //return ta-tb in microseconds (no overflow checks!)
61 inline long timediff(const struct timespec *ta,const struct timespec *tb) {
62 unsigned long us = (ta->tv_sec-tb->tv_sec)*1000000 + (ta->tv_nsec/1000 - tb->tv_nsec/1000);
66 int waitforpid(int pid) {
67 //switch to low priority
68 // if (setpriority(PRIO_PROCESS,getpid(),19)!=0) {
69 if ( (nice_lim < INT_MAX) &&
70 (setpriority(PRIO_PROCESS, getpid(), 19) != 0) ) {
71 printf("Warning: cannot renice\n");
81 //open a directory stream to /proc directory
82 if ((dip = opendir("/proc")) == NULL) {
87 //read in from /proc and seek for process dirs
88 while ((dit = readdir(dip)) != NULL) {
90 if (pid==atoi(dit->d_name)) {
92 if (kill(pid,SIGSTOP)==0 && kill(pid,SIGCONT)==0) {
94 if (closedir(dip) == -1) {
101 fprintf(stderr,"Error: Process %d detected, but you don't have permission to control it\n",pid);
106 //close the dir stream and check for errors
107 if (closedir(dip) == -1) {
112 //no suitable target found
115 fprintf(stderr,"No process found\n");
119 printf("Warning: no target process found. Waiting for it...\n");
128 printf("Process %d detected\n",pid);
129 //now set high priority, if possible
130 // if (setpriority(PRIO_PROCESS,getpid(),-20)!=0) {
131 if ( (nice_lim < INT_MAX) &&
132 (setpriority(PRIO_PROCESS, getpid(), nice_lim) != 0) ) {
133 printf("Warning: cannot renice.\nTo work better you should run this program as root.\n");
139 //this function periodically scans process list and looks for executable path names
140 //it should be executed in a low priority context, since precise timing does not matter
141 //if a process is found then its pid is returned
142 //process: the name of the wanted process, can be an absolute path name to the executable file
143 // or simply its name
144 //return: pid of the found process
145 int getpidof(const char *process) {
148 // if (setpriority(PRIO_PROCESS,getpid(),19)!=0) {
149 if ( (nice_lim < INT_MAX) &&
150 (setpriority(PRIO_PROCESS, getpid(), 19) != 0) ) {
151 printf("Warning: cannot renice\n");
155 char exepath[PATH_MAX+1];
164 //open a directory stream to /proc directory
165 if ((dip = opendir("/proc")) == NULL) {
170 //read in from /proc and seek for process dirs
171 while ((dit = readdir(dip)) != NULL) {
173 pid=atoi(dit->d_name);
175 sprintf(exelink,"/proc/%d/exe",pid);
176 int size=readlink(exelink,exepath,sizeof(exepath));
179 if (process[0]=='/' && strncmp(exepath,process,size)==0 && size==strlen(process)) {
180 //process starts with / then it's an absolute path
184 //process is the name of the executable file
185 if (strncmp(exepath+size-strlen(process),process,strlen(process))==0) {
190 if (kill(pid,SIGSTOP)==0 && kill(pid,SIGCONT)==0) {
192 if (closedir(dip) == -1) {
199 fprintf(stderr,"Error: Process %d detected, but you don't have permission to control it\n",pid);
206 //close the dir stream and check for errors
207 if (closedir(dip) == -1) {
212 //no suitable target found
215 fprintf(stderr,"No process found\n");
219 printf("Warning: no target process found. Waiting for it...\n");
228 printf("Process %d detected\n",pid);
229 //now set high priority, if possible
230 // if (setpriority(PRIO_PROCESS,getpid(),-20)!=0) {
231 if ( (nice_lim < INT_MAX) &&
232 (setpriority(PRIO_PROCESS, getpid(), nice_lim) != 0) ) {
233 printf("Warning: cannot renice.\nTo work better you should run this program as root.\n");
239 //SIGINT and SIGTERM signal handler
241 //let the process continue if it's stopped
243 printf("Exiting...\n");
247 //get jiffies count from /proc filesystem
248 int getjiffies(int pid) {
249 static char stat[20];
250 static char buffer[1024];
252 sprintf(stat,"/proc/%d/stat",pid);
253 FILE *f=fopen(stat,"r");
254 if (f==NULL) return -1;
255 p = fgets(buffer,sizeof(buffer),f);
260 p=memchr(p+1,')',sizeof(buffer)-(p-buffer));
263 p=memchr(p+1,' ',sizeof(buffer)-(p-buffer));
266 p=memchr(p+1,' ',sizeof(buffer)-(p-buffer));
267 //kernel mode jiffies
271 // could not read info
275 //process instant photo
276 struct process_screenshot {
277 struct timespec when; //timestamp
278 int jiffies; //jiffies count of the process
279 int cputime; //microseconds of work from previous screenshot to current
282 //extracted process statistics
288 //this function is an autonomous dynamic system
289 //it works with static variables (state variables of the system), that keep memory of recent past
290 //its aim is to estimate the cpu usage of the process
291 //to work properly it should be called in a fixed periodic way
292 //perhaps i will put it in a separate thread...
293 int compute_cpu_usage(int pid,int last_working_quantum,struct cpu_usage *pusage) {
295 //circular buffer containing last MEM_ORDER process screenshots
296 static struct process_screenshot ps[MEM_ORDER];
297 //the last screenshot recorded in the buffer
299 //the oldest screenshot recorded in the buffer
303 //reinit static variables
309 //let's advance front index and save the screenshot
310 front=(front+1)%MEM_ORDER;
311 int j=getjiffies(pid);
312 if (j>=0) ps[front].jiffies=j;
313 else return -1; //error: pid does not exist
314 clock_gettime(CLOCK_REALTIME,&(ps[front].when));
315 ps[front].cputime=last_working_quantum;
317 //buffer actual size is: (front-tail+MEM_ORDER)%MEM_ORDER+1
318 int size=(front-tail+MEM_ORDER)%MEM_ORDER+1;
321 //not enough samples taken (it's the first one!), return -1
323 pusage->workingrate=1;
327 //now we can calculate cpu usage, interval dt and dtwork are expressed in microseconds
328 long dt=timediff(&(ps[front].when),&(ps[tail].when));
330 int i=(tail+1)%MEM_ORDER;
331 int max=(front+1)%MEM_ORDER;
333 dtwork+=ps[i].cputime;
336 int used=ps[front].jiffies-ps[tail].jiffies;
337 float usage=(used*1000000.0/HZ)/dtwork;
338 pusage->workingrate=1.0*dtwork/dt;
339 pusage->pcpu=usage*pusage->workingrate;
341 tail=(tail+1)%MEM_ORDER;
347 void print_caption() {
348 printf("\n%%CPU\twork quantum\tsleep quantum\tactive rate\n");
351 void print_usage(FILE *stream,int exit_code) {
352 fprintf(stream, "Usage: %s TARGET [OPTIONS...]\n",program_name);
353 fprintf(stream, " TARGET must be exactly one of these:\n");
354 fprintf(stream, " -p, --pid=N pid of the process\n");
355 fprintf(stream, " -e, --exe=FILE name of the executable program file\n");
356 fprintf(stream, " -P, --path=PATH absolute path name of the executable program file\n");
357 fprintf(stream, " OPTIONS\n");
358 fprintf(stream, " -b --background run in background\n");
359 fprintf(stream, " -l, --limit=N percentage of cpu allowed from 0 to 100 (mandatory)\n");
360 fprintf(stream, " -v, --verbose show control statistics\n");
361 fprintf(stream, " -z, --lazy exit if there is no suitable target process, or if it dies\n");
362 fprintf(stream, " -h, --help display this help and exit\n");
366 int main(int argc, char **argv) {
369 char *p=(char*)memrchr(argv[0],(unsigned int)'/',strlen(argv[0]));
370 program_name = p==NULL?argv[0]:(p+1);
371 int run_in_background = 0;
374 /* A string listing valid short options letters. */
375 const char* short_options="p:e:P:l:bvzh";
376 /* An array describing valid long options. */
377 const struct option long_options[] = {
378 { "pid", required_argument, NULL, 'p' },
379 { "exe", required_argument, NULL, 'e' },
380 { "path", required_argument, NULL, 'P' },
381 { "limit", required_argument, NULL, 'l' },
382 { "background", no_argument, NULL, 'b' },
383 { "verbose", no_argument, NULL, 'v' },
384 { "lazy", no_argument, NULL, 'z' },
385 { "help", no_argument, NULL, 'h' },
389 const char *exe=NULL;
390 const char *path=NULL;
395 struct rlimit maxlimit;
398 next_option = getopt_long (argc, argv, short_options,long_options, NULL);
399 switch(next_option) {
401 run_in_background = 1;
417 perclimit=atoi(optarg);
427 print_usage (stdout, 1);
430 print_usage (stderr, 1);
437 } while(next_option != -1);
439 if (!process_ok && !pid_ok) {
440 fprintf(stderr,"Error: You must specify a target process\n");
441 print_usage (stderr, 1);
444 if ((exe!=NULL && path!=NULL) || (pid_ok && (exe!=NULL || path!=NULL))) {
445 fprintf(stderr,"Error: You must specify exactly one target process\n");
446 print_usage (stderr, 1);
450 fprintf(stderr,"Error: You must specify a cpu limit\n");
451 print_usage (stderr, 1);
454 float limit=perclimit/100.0;
455 if (limit <= 0.00) // || limit >1) {
457 fprintf(stderr,"Error: limit must be greater than 0\n");
458 print_usage (stderr, 1);
462 // check to see if we should fork
463 if (run_in_background)
478 //parameters are all ok!
480 signal(SIGTERM,quit);
482 if (setpriority(PRIO_PROCESS,getpid(),-20)!=0) {
483 //if that failed, check if we have a limit
484 // by how much we can raise the priority
486 //check if non-root can even make changes
487 // (ifdef because it's only available in linux >= 2.6.13)
488 nice_lim=getpriority(PRIO_PROCESS,getpid());
489 getrlimit(RLIMIT_NICE, &maxlimit);
491 //if we can do better then current
492 if( (20 - (signed)maxlimit.rlim_cur) < nice_lim &&
493 setpriority(PRIO_PROCESS,getpid(),
494 20 - (signed)maxlimit.rlim_cur)==0 //and it actually works
497 //if we can do better, but not by much, warn about it
498 if( (nice_lim - (20 - (signed)maxlimit.rlim_cur)) < 9)
500 printf("Warning, can only increase priority by %d.\n", nice_lim - (20 - (signed)maxlimit.rlim_cur));
503 nice_lim = 20 - (signed)maxlimit.rlim_cur;
506 // otherwise don't try to change priority.
507 // The below will also run if it's not possible
508 // for non-root to change priority
511 printf("Warning: cannot renice.\nTo work better you should run this program as root, or adjust RLIMIT_NICE.\nFor example in /etc/security/limits.conf add a line with: * - nice -10\n\n");
517 //don't bother putting setpriority back down,
518 // since getpidof and waitforpid twiddle it anyway
522 //time quantum in microseconds. it's splitted in a working period and a sleeping one
524 struct timespec twork,tsleep; //working and sleeping intervals
525 memset(&twork,0,sizeof(struct timespec));
526 memset(&tsleep,0,sizeof(struct timespec));
530 //look for the target process..or wait for it
538 //process detected...let's play
540 //init compute_cpu_usage internal stuff
541 compute_cpu_usage(0,0,NULL);
545 struct timespec startwork,endwork;
546 long workingtime=0; //last working time in microseconds
548 if (verbose) print_caption();
552 //here we should already have high priority, for time precision
555 //estimate how much the controlled process is using the cpu in its working interval
557 if (compute_cpu_usage(pid,workingtime,&cu)==-1) {
558 fprintf(stderr,"Process %d dead!\n",pid);
560 //wait until our process appears
561 goto wait_for_process;
564 //cpu actual usage of process (range 0-1)
566 //rate at which we are keeping active the process (range 0-1)
567 float workingrate=cu.workingrate;
569 //adjust work and sleep time slices
571 twork.tv_nsec=min(period*limit*1000/pcpu*workingrate,period*1000);
574 twork.tv_nsec=period*1000;
577 //not yet a valid idea of cpu usage
580 twork.tv_nsec=min(period*limit*1000,period*1000);
582 tsleep.tv_nsec=period*1000-twork.tv_nsec;
584 //update average usage
585 pcpu_avg=(pcpu_avg*i+pcpu)/(i+1);
587 if (verbose && i%10==0 && i>0) {
588 printf("%0.2f%%\t%6ld us\t%6ld us\t%0.2f%%\n",pcpu*100,twork.tv_nsec/1000,tsleep.tv_nsec/1000,workingrate*100);
591 if (limit<1 && limit>0) {
593 if (kill(pid,SIGCONT)!=0) {
594 fprintf(stderr,"Process %d dead!\n",pid);
596 //wait until our process appears
597 goto wait_for_process;
601 clock_gettime(CLOCK_REALTIME,&startwork);
602 nanosleep(&twork,NULL); //now process is working
603 clock_gettime(CLOCK_REALTIME,&endwork);
604 workingtime=timediff(&endwork,&startwork);
607 //stop process, it has worked enough
608 if (kill(pid,SIGSTOP)!=0) {
609 fprintf(stderr,"Process %d dead!\n",pid);
611 //wait until our process appears
612 goto wait_for_process;
614 nanosleep(&tsleep,NULL); //now process is sleeping