- remove all patches (applied upstream), remove quilt framework
[debian/cpulimit.git] / cpulimit.c
1 /**
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
4  * LICENSE file.
5  *
6  **********************************************************************
7  *
8  * Simple program to limit the cpu usage of a process
9  * If you modify this code, send me a copy please
10  *
11  * Author:  Angelo Marletta
12  * Date:    26/06/2005
13  * Version: 1.1
14  *
15  * Modifications and updates by: Jesse Smith
16  * Date: May 4, 2011
17  * Version 1.2
18  *
19  */
20
21
22 #include <getopt.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <time.h>
26 #include <sys/time.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <signal.h>
30 #include <sys/resource.h>
31 #include <string.h>
32 #include <dirent.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <limits.h>    // for compatibility
36
37
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!)
40 #define HZ 100
41
42 //some useful macro
43 #define min(a,b) (a<b?a:b)
44 #define max(a,b) (a>b?a:b)
45
46 //pid of the controlled process
47 int pid=0;
48 //executable file name
49 char *program_name;
50 //verbose mode
51 int verbose=0;
52 //lazy mode
53 int lazy=0;
54 // is higher priority nice possible?
55 int nice_lim;
56
57 //reverse byte search
58 void *memrchr(const void *s, int c, size_t n);
59
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);
63     return us;
64 }
65
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");
72         }
73
74         int i=0;
75
76         while(1) {
77
78                 DIR *dip;
79                 struct dirent *dit;
80
81                 //open a directory stream to /proc directory
82                 if ((dip = opendir("/proc")) == NULL) {
83                         perror("opendir");
84                         return -1;
85                 }
86
87                 //read in from /proc and seek for process dirs
88                 while ((dit = readdir(dip)) != NULL) {
89                         //get pid
90                         if (pid==atoi(dit->d_name)) {
91                                 //pid detected
92                                 if (kill(pid,SIGSTOP)==0 &&  kill(pid,SIGCONT)==0) {
93                                         //process is ok!
94                                         if (closedir(dip) == -1) {
95                                            perror("closedir");
96                                            return -1;
97                                         }
98                                         goto done;
99                                 }
100                                 else {
101                                         fprintf(stderr,"Error: Process %d detected, but you don't have permission to control it\n",pid);
102                                 }
103                         }
104                 }
105
106                 //close the dir stream and check for errors
107                 if (closedir(dip) == -1) {
108                         perror("closedir");
109                         return -1;
110                 }
111
112                 //no suitable target found
113                 if (i++==0) {
114                         if (lazy) {
115                                 fprintf(stderr,"No process found\n");
116                                 exit(2);
117                         }
118                         else {
119                                 printf("Warning: no target process found. Waiting for it...\n");
120                         }
121                 }
122
123                 //sleep for a while
124                 sleep(2);
125         }
126
127 done:
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");
134         }
135         return 0;
136
137 }
138
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) {
146
147         //set low priority
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");
152         }
153
154         char exelink[20];
155         char exepath[PATH_MAX+1];
156         int pid=0;
157         int i=0;
158
159         while(1) {
160
161                 DIR *dip;
162                 struct dirent *dit;
163
164                 //open a directory stream to /proc directory
165                 if ((dip = opendir("/proc")) == NULL) {
166                         perror("opendir");
167                         return -1;
168                 }
169
170                 //read in from /proc and seek for process dirs
171                 while ((dit = readdir(dip)) != NULL) {
172                         //get pid
173                         pid=atoi(dit->d_name);
174                         if (pid>0) {
175                                 sprintf(exelink,"/proc/%d/exe",pid);
176                                 int size=readlink(exelink,exepath,sizeof(exepath));
177                                 if (size>0) {
178                                         int found=0;
179                                         if (process[0]=='/' && strncmp(exepath,process,size)==0 && size==strlen(process)) {
180                                                 //process starts with / then it's an absolute path
181                                                 found=1;
182                                         }
183                                         else {
184                                                 //process is the name of the executable file
185                                                 if (strncmp(exepath+size-strlen(process),process,strlen(process))==0) {
186                                                         found=1;
187                                                 }
188                                         }
189                                         if (found==1) {
190                                                 if (kill(pid,SIGSTOP)==0 &&  kill(pid,SIGCONT)==0) {
191                                                         //process is ok!
192                                                         if (closedir(dip) == -1) {
193                                                           perror("closedir");
194                                                           return -1;
195                                                         }
196                                                         goto done;
197                                                 }
198                                                 else {
199                                                         fprintf(stderr,"Error: Process %d detected, but you don't have permission to control it\n",pid);
200                                                 }
201                                         }
202                                 }
203                         }
204                 }
205
206                 //close the dir stream and check for errors
207                 if (closedir(dip) == -1) {
208                         perror("closedir");
209                         return -1;
210                 }
211
212                 //no suitable target found
213                 if (i++==0) {
214                         if (lazy) {
215                                 fprintf(stderr,"No process found\n");
216                                 exit(2);
217                         }
218                         else {
219                                 printf("Warning: no target process found. Waiting for it...\n");
220                         }
221                 }
222
223                 //sleep for a while
224                 sleep(2);
225         }
226
227 done:
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");
234         }
235         return pid;
236
237 }
238
239 //SIGINT and SIGTERM signal handler
240 void quit(int sig) {
241         //let the process continue if it's stopped
242         kill(pid,SIGCONT);
243         printf("Exiting...\n");
244         exit(0);
245 }
246
247 //get jiffies count from /proc filesystem
248 int getjiffies(int pid) {
249         static char stat[20];
250         static char buffer[1024];
251         sprintf(stat,"/proc/%d/stat",pid);
252         FILE *f=fopen(stat,"r");
253         if (f==NULL) return -1;
254         fgets(buffer,sizeof(buffer),f);
255         fclose(f);
256         char *p=buffer;
257         p=memchr(p+1,')',sizeof(buffer)-(p-buffer));
258         int sp=12;
259         while (sp--)
260                 p=memchr(p+1,' ',sizeof(buffer)-(p-buffer));
261         //user mode jiffies
262         int utime=atoi(p+1);
263         p=memchr(p+1,' ',sizeof(buffer)-(p-buffer));
264         //kernel mode jiffies
265         int ktime=atoi(p+1);
266         return utime+ktime;
267 }
268
269 //process instant photo
270 struct process_screenshot {
271         struct timespec when;   //timestamp
272         int jiffies;    //jiffies count of the process
273         int cputime;    //microseconds of work from previous screenshot to current
274 };
275
276 //extracted process statistics
277 struct cpu_usage {
278         float pcpu;
279         float workingrate;
280 };
281
282 //this function is an autonomous dynamic system
283 //it works with static variables (state variables of the system), that keep memory of recent past
284 //its aim is to estimate the cpu usage of the process
285 //to work properly it should be called in a fixed periodic way
286 //perhaps i will put it in a separate thread...
287 int compute_cpu_usage(int pid,int last_working_quantum,struct cpu_usage *pusage) {
288         #define MEM_ORDER 10
289         //circular buffer containing last MEM_ORDER process screenshots
290         static struct process_screenshot ps[MEM_ORDER];
291         //the last screenshot recorded in the buffer
292         static int front=-1;
293         //the oldest screenshot recorded in the buffer
294         static int tail=0;
295
296         if (pusage==NULL) {
297                 //reinit static variables
298                 front=-1;
299                 tail=0;
300                 return 0;
301         }
302
303         //let's advance front index and save the screenshot
304         front=(front+1)%MEM_ORDER;
305         int j=getjiffies(pid);
306         if (j>=0) ps[front].jiffies=j;
307         else return -1; //error: pid does not exist
308         clock_gettime(CLOCK_REALTIME,&(ps[front].when));
309         ps[front].cputime=last_working_quantum;
310
311         //buffer actual size is: (front-tail+MEM_ORDER)%MEM_ORDER+1
312         int size=(front-tail+MEM_ORDER)%MEM_ORDER+1;
313
314         if (size==1) {
315                 //not enough samples taken (it's the first one!), return -1
316                 pusage->pcpu=-1;
317                 pusage->workingrate=1;
318                 return 0;
319         }
320         else {
321                 //now we can calculate cpu usage, interval dt and dtwork are expressed in microseconds
322                 long dt=timediff(&(ps[front].when),&(ps[tail].when));
323                 long dtwork=0;
324                 int i=(tail+1)%MEM_ORDER;
325                 int max=(front+1)%MEM_ORDER;
326                 do {
327                         dtwork+=ps[i].cputime;
328                         i=(i+1)%MEM_ORDER;
329                 } while (i!=max);
330                 int used=ps[front].jiffies-ps[tail].jiffies;
331                 float usage=(used*1000000.0/HZ)/dtwork;
332                 pusage->workingrate=1.0*dtwork/dt;
333                 pusage->pcpu=usage*pusage->workingrate;
334                 if (size==MEM_ORDER)
335                         tail=(tail+1)%MEM_ORDER;
336                 return 0;
337         }
338         #undef MEM_ORDER
339 }
340
341 void print_caption() {
342         printf("\n%%CPU\twork quantum\tsleep quantum\tactive rate\n");
343 }
344
345 void print_usage(FILE *stream,int exit_code) {
346         fprintf(stream, "Usage: %s TARGET [OPTIONS...]\n",program_name);
347         fprintf(stream, "   TARGET must be exactly one of these:\n");
348         fprintf(stream, "      -p, --pid=N        pid of the process\n");
349         fprintf(stream, "      -e, --exe=FILE     name of the executable program file\n");
350         fprintf(stream, "      -P, --path=PATH    absolute path name of the executable program file\n");
351         fprintf(stream, "   OPTIONS\n");
352         fprintf(stream, "      -b  --background   run in background\n");
353         fprintf(stream, "      -l, --limit=N      percentage of cpu allowed from 0 to 100 (mandatory)\n");
354         fprintf(stream, "      -v, --verbose      show control statistics\n");
355         fprintf(stream, "      -z, --lazy         exit if there is no suitable target process, or if it dies\n");
356         fprintf(stream, "      -h, --help         display this help and exit\n");
357         exit(exit_code);
358 }
359
360 int main(int argc, char **argv) {
361
362         //get program name
363         char *p=(char*)memrchr(argv[0],(unsigned int)'/',strlen(argv[0]));
364         program_name = p==NULL?argv[0]:(p+1);
365         int run_in_background = 0;
366         //parse arguments
367         int next_option;
368         /* A string listing valid short options letters. */
369         const char* short_options="p:e:P:l:bvzh";
370         /* An array describing valid long options. */
371         const struct option long_options[] = {
372                 { "pid", required_argument, NULL, 'p' },
373                 { "exe", required_argument, NULL, 'e' },
374                 { "path", required_argument, NULL, 'P' },
375                 { "limit", required_argument, NULL, 'l' },
376                 { "background", no_argument, NULL, 'b' },
377                 { "verbose", no_argument, NULL, 'v' },
378                 { "lazy", no_argument, NULL, 'z' },
379                 { "help", no_argument, NULL, 'h' },
380                 { NULL, 0, NULL, 0 }
381         };
382         //argument variables
383         const char *exe=NULL;
384         const char *path=NULL;
385         int perclimit=0;
386         int pid_ok=0;
387         int process_ok=0;
388         int limit_ok=0;
389         struct rlimit maxlimit;
390
391         do {
392                 next_option = getopt_long (argc, argv, short_options,long_options, NULL);
393                 switch(next_option) {
394                         case 'b':
395                                 run_in_background = 1;
396                                 break;
397                         case 'p':
398                                 pid=atoi(optarg);
399                                 pid_ok=1;
400                                 lazy = 1;
401                                 break;
402                         case 'e':
403                                 exe=optarg;
404                                 process_ok=1;
405                                 break;
406                         case 'P':
407                                 path=optarg;
408                                 process_ok=1;
409                                 break;
410                         case 'l':
411                                 perclimit=atoi(optarg);
412                                 limit_ok=1;
413                                 break;
414                         case 'v':
415                                 verbose=1;
416                                 break;
417                         case 'z':
418                                 lazy=1;
419                                 break;
420                         case 'h':
421                                 print_usage (stdout, 1);
422                                 break;
423                         case '?':
424                                 print_usage (stderr, 1);
425                                 break;
426                         case -1:
427                                 break;
428                         default:
429                                 abort();
430                 }
431         } while(next_option != -1);
432
433         if (!process_ok && !pid_ok) {
434                 fprintf(stderr,"Error: You must specify a target process\n");
435                 print_usage (stderr, 1);
436                 exit(1);
437         }
438         if ((exe!=NULL && path!=NULL) || (pid_ok && (exe!=NULL || path!=NULL))) {
439                 fprintf(stderr,"Error: You must specify exactly one target process\n");
440                 print_usage (stderr, 1);
441                 exit(1);
442         }
443         if (!limit_ok) {
444                 fprintf(stderr,"Error: You must specify a cpu limit\n");
445                 print_usage (stderr, 1);
446                 exit(1);
447         }
448         float limit=perclimit/100.0;
449         if (limit<0 || limit >1) {
450                 fprintf(stderr,"Error: limit must be in the range 0-100\n");
451                 print_usage (stderr, 1);
452                 exit(1);
453         }
454
455         // check to see if we should fork
456         if (run_in_background)
457         {
458              pid_t process_id;
459              process_id = fork();
460              if (! process_id)
461                 exit(0);
462              else
463              {
464                 setsid();
465                 process_id = fork();
466                 if (process_id)
467                   exit(0);
468              }
469         }
470
471         //parameters are all ok!
472         signal(SIGINT,quit);
473         signal(SIGTERM,quit);
474
475         if (setpriority(PRIO_PROCESS,getpid(),-20)!=0) {
476         //if that failed, check if we have a limit 
477         // by how much we can raise the priority
478 #ifdef RLIMIT_NICE 
479 //check if non-root can even make changes 
480 // (ifdef because it's only available in linux >= 2.6.13)
481                 nice_lim=getpriority(PRIO_PROCESS,getpid());
482                 getrlimit(RLIMIT_NICE, &maxlimit);
483
484 //if we can do better then current
485                 if( (20 - (signed)maxlimit.rlim_cur) < nice_lim &&  
486                     setpriority(PRIO_PROCESS,getpid(),
487                     20 - (signed)maxlimit.rlim_cur)==0 //and it actually works
488                   ) {
489
490                         //if we can do better, but not by much, warn about it
491                         if( (nice_lim - (20 - (signed)maxlimit.rlim_cur)) < 9) 
492                         {
493                         printf("Warning, can only increase priority by %d.\n",                                nice_lim - (20 - (signed)maxlimit.rlim_cur));
494                         }
495                         //our new limit
496                         nice_lim = 20 - (signed)maxlimit.rlim_cur; 
497
498                 } else 
499 // otherwise don't try to change priority. 
500 // The below will also run if it's not possible 
501 // for non-root to change priority
502 #endif
503                 {
504                         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");
505                         nice_lim=INT_MAX;
506                 }
507         } else {
508                 nice_lim=-20;
509         }
510         //don't bother putting setpriority back down, 
511         // since getpidof and waitforpid twiddle it anyway
512
513
514
515         //time quantum in microseconds. it's splitted in a working period and a sleeping one
516         int period=100000;
517         struct timespec twork,tsleep;   //working and sleeping intervals
518         memset(&twork,0,sizeof(struct timespec));
519         memset(&tsleep,0,sizeof(struct timespec));
520
521 wait_for_process:
522
523         //look for the target process..or wait for it
524         if (exe!=NULL)
525                 pid=getpidof(exe);
526         else if (path!=NULL)
527                 pid=getpidof(path);
528         else {
529                 waitforpid(pid);
530         }
531         //process detected...let's play
532
533         //init compute_cpu_usage internal stuff
534         compute_cpu_usage(0,0,NULL);
535         //main loop counter
536         int i=0;
537
538         struct timespec startwork,endwork;
539         long workingtime=0;             //last working time in microseconds
540
541         if (verbose) print_caption();
542
543         float pcpu_avg=0;
544
545         //here we should already have high priority, for time precision
546         while(1) {
547
548                 //estimate how much the controlled process is using the cpu in its working interval
549                 struct cpu_usage cu;
550                 if (compute_cpu_usage(pid,workingtime,&cu)==-1) {
551                         fprintf(stderr,"Process %d dead!\n",pid);
552                         if (lazy) exit(2);
553                         //wait until our process appears
554                         goto wait_for_process;          
555                 }
556
557                 //cpu actual usage of process (range 0-1)
558                 float pcpu=cu.pcpu;
559                 //rate at which we are keeping active the process (range 0-1)
560                 float workingrate=cu.workingrate;
561
562                 //adjust work and sleep time slices
563                 if (pcpu>0) {
564                         twork.tv_nsec=min(period*limit*1000/pcpu*workingrate,period*1000);
565                 }
566                 else if (pcpu==0) {
567                         twork.tv_nsec=period*1000;
568                 }
569                 else if (pcpu==-1) {
570                         //not yet a valid idea of cpu usage
571                         pcpu=limit;
572                         workingrate=limit;
573                         twork.tv_nsec=min(period*limit*1000,period*1000);
574                 }
575                 tsleep.tv_nsec=period*1000-twork.tv_nsec;
576
577                 //update average usage
578                 pcpu_avg=(pcpu_avg*i+pcpu)/(i+1);
579
580                 if (verbose && i%10==0 && i>0) {
581                         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);
582                 }
583
584                 if (limit<1 && limit>0) {
585                         //resume process
586                         if (kill(pid,SIGCONT)!=0) {
587                                 fprintf(stderr,"Process %d dead!\n",pid);
588                                 if (lazy) exit(2);
589                                 //wait until our process appears
590                                 goto wait_for_process;
591                         }
592                 }
593
594                 clock_gettime(CLOCK_REALTIME,&startwork);
595                 nanosleep(&twork,NULL);         //now process is working        
596                 clock_gettime(CLOCK_REALTIME,&endwork);
597                 workingtime=timediff(&endwork,&startwork);
598
599                 if (limit<1) {
600                         //stop process, it has worked enough
601                         if (kill(pid,SIGSTOP)!=0) {
602                                 fprintf(stderr,"Process %d dead!\n",pid);
603                                 if (lazy) exit(2);
604                                 //wait until our process appears
605                                 goto wait_for_process;
606                         }
607                         nanosleep(&tsleep,NULL);        //now process is sleeping
608                 }
609                 i++;
610         }
611
612 }