Imported Upstream version 1.8 upstream/1.8
authorgregor herrmann <gregoa@debian.org>
Sun, 16 Sep 2012 12:40:15 +0000 (14:40 +0200)
committergregor herrmann <gregoa@debian.org>
Sun, 16 Sep 2012 12:40:15 +0000 (14:40 +0200)
CHANGELOG
Makefile
README
cpulimit.c
cpulimit.orig [new file with mode: 0644]
test/Makefile [new file with mode: 0644]
test/busy.c [new file with mode: 0644]

index 5df3d59..1db1087 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,34 @@
+=========== Changes in 1.8 ================
+
+* Added separate build targets for Linux, FreeBSD and OS X.
+  See the compiling section of the README file.
+
+* Applied patch from FreeBSD to our upstream code to make
+  future updates on FreeBSD easier.
+
+* When displaying verbose output, cpulimit now redisplays
+  the column headers every 20 lines.
+
+* Fixed limiting CPU usage on multicore machines when
+  the desired usage limit is great than 100%.
+  ie A machine with 4 cores can now properly limit
+  multi-threaded applications to, say, 150% or 250%.
+
+* Added test application (busy). See "test" directory.
+  To build the test software, run "make tests".
+
+* Removed memrchr() call to allow program to compile on OS X.
+
+* Updated Makefile to work with OS X.
+
+* Fixed OS X compiler error. Macs do not understand
+  CLOCK_REALTIME
+
+* Added patch from Debian which added compiler flags. This
+  should "harden" CPUlimit on Debian.
+  Thanks to Gregor for supplying this patch.
+
+
 ============= Changes in 1.7 ===============
 
 * Minor code cleanup.
index 4de68b5..4d62028 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,21 @@
-VERSION?=1.7
+VERSION?=1.8
 PREFIX?=/usr
 CFLAGS?=-Wall -O2
 CC?=gcc
 
 all: cpulimit
 
+osx:
+       $(CC) -o cpulimit cpulimit.c -D__MACH__ $(CFLAGS) $(CPPFLAGS) $(LDFLAGS)
+
+freebsd:
+       $(CC) -o cpulimit cpulimit.c -lrt -DFREEBSD $(CFLAGS) $(CPPFLAGS) $(LDFLAGS)
+
 cpulimit: cpulimit.c
-       $(CC) -o cpulimit cpulimit.c -lrt $(CFLAGS)
+       $(CC) -o cpulimit cpulimit.c -lrt -DLINUX $(CFLAGS) $(CPPFLAGS) $(LDFLAGS)
+
+tests:
+       $(MAKE) -C test
 
 install: cpulimit
        mkdir -p ${PREFIX}/bin
@@ -20,6 +29,7 @@ deinstall:
 
 clean:
        rm -f *~ cpulimit
+       $(MAKE) -C test clean
 
 tarball: clean
        cd .. && tar czf cpulimit-$(VERSION).tar.gz cpulimit-$(VERSION)
diff --git a/README b/README
index 1d5ab92..813767d 100644 (file)
--- a/README
+++ b/README
@@ -42,11 +42,11 @@ How to compile and install
 Once you have downloaded a copy of LimitCPU building should be fairly
 straight forward. First we unpack the source code
 
-tar zxf cpulimit-1.7.tar.gz
+tar zxf cpulimit-1.8.tar.gz
 
 Then we run the makefile.
 
-cd cpulimit-1.7
+cd cpulimit-1.8
 make
 
 This should produce the executable file "cpulimit". If you would like
@@ -62,6 +62,22 @@ make deinstall
 
 
 
+Note on compiling for non-Linux operating systems:
+LimitCPU can be compiled on FreeBSD and OS X (though at this time
+LimitCPU has not been tested and may not work properly on OS X).
+To compile LimitCPU on these platforms, instead of running "make"
+to build the executable, instead run either
+
+make freebsd
+or
+make osx
+
+in order to build on FreeBSD or OS X respectively.
+
+
+
+
+
 Common usage
 ==========================
 
index 9d3d0a1..ef1b6c2 100644 (file)
 #include <string.h>
 #include <limits.h>    // for compatibility
 
+#ifdef __MACH__
+#include <mach/clock.h>
+#include <mach/mach.h>
+#endif
+
+
+#ifdef FREEBSD
+#include <fcntl.h>
+#include <kvm.h>
+#include <paths.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#endif
 
 
 //kernel time resolution (inverse of one jiffy interval) in Hertz
@@ -81,7 +95,9 @@ int nice_lim;
 int NCPU;
 
 //reverse byte search
-void *memrchr(const void *s, int c, size_t n);
+// void *memrchr(const void *s, int c, size_t n);
+
+
 
 //return ta-tb in microseconds (no overflow checks!)
 inline long timediff(const struct timespec *ta,const struct timespec *tb) {
@@ -294,7 +310,36 @@ void quit(int sig) {
        exit(0);
 }
 
+
+#ifdef FREEBSD
 //get jiffies count from /proc filesystem
+int getjiffies(int pid)
+{
+   kvm_t *my_kernel = NULL;
+   struct kinfo_proc *process_data = NULL;
+   int processes;
+   int my_jiffies = -1;
+
+   my_kernel = kvm_open(0, 0, 0, O_RDONLY, "kvm_open");
+   if (! my_kernel)
+   {
+      printf("Error opening kernel vm. You should be running as root.\n");
+      return -1;
+   }
+
+   process_data = kvm_getprocs(my_kernel, KERN_PROC_PID, pid, &processes);
+   if ( (process_data) && (processes >= 1) )
+       my_jiffies = process_data->ki_runtime;
+   
+   kvm_close(my_kernel);
+   if (my_jiffies >= 0)
+     my_jiffies /= 1000;
+   return my_jiffies;
+}
+
+#endif
+
+#ifdef LINUX
 int getjiffies(int pid) {
        static char stat[20];
        static char buffer[1024];
@@ -321,6 +366,8 @@ int getjiffies(int pid) {
         // could not read info
         return -1;
 }
+#endif
+
 
 //process instant photo
 struct process_screenshot {
@@ -361,9 +408,22 @@ int compute_cpu_usage(int pid,int last_working_quantum,struct cpu_usage *pusage)
        int j=getjiffies(pid);
        if (j>=0) ps[front].jiffies=j;
        else return -1; //error: pid does not exist
+
+        #ifdef __MACH__ 
+        // OS X does not have clock_gettime, use clock_get_time
+        clock_serv_t cclock;
+        mach_timespec_t mts;
+        host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+        clock_get_time(cclock, &mts);
+        mach_port_deallocate(mach_task_self(), cclock);
+        ps[front].when.tv_sec = mts.tv_sec;
+        ps[front].when.tv_nsec = mts.tv_nsec;
+
+        #else
+        // Linux and BSD can use real time
        clock_gettime(CLOCK_REALTIME,&(ps[front].when));
        ps[front].cputime=last_working_quantum;
-
+        #endif
        //buffer actual size is: (front-tail+MEM_ORDER)%MEM_ORDER+1
        int size=(front-tail+MEM_ORDER)%MEM_ORDER+1;
 
@@ -462,8 +522,9 @@ int get_ncpu()
 int main(int argc, char **argv) {
 
        //get program name
-       char *p=(char*)memrchr(argv[0],(unsigned int)'/',strlen(argv[0]));
-       program_name = p==NULL?argv[0]:(p+1);
+       // char *p=(char*)memrchr(argv[0],(unsigned int)'/',strlen(argv[0]));
+       // program_name = p==NULL?argv[0]:(p+1);
+        program_name = argv[0];
         int run_in_background = FALSE;
        //parse arguments
        int next_option;
@@ -773,9 +834,15 @@ wait_for_process:
 
                if (verbose && i%10==0 && i>0) {
                        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);
+                        if (i%200 == 0)
+                           print_caption();
                }
 
-               if (limit<1 && limit>0) {
+               // if (limit<1 && limit>0) {
+                // printf("Comparing %f to %f\n", pcpu, limit);
+                if (pcpu < limit)
+                {
+                        // printf("Continue\n");
                        //resume process
                        if (kill(pid,SIGCONT)!=0) {
                                fprintf(stderr,"Process %d dead!\n",pid);
@@ -785,12 +852,41 @@ wait_for_process:
                        }
                }
 
+                #ifdef __MACH__ 
+                // OS X does not have clock_gettime, use clock_get_time
+                clock_serv_t cclock;
+                mach_timespec_t mts;
+                host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+                clock_get_time(cclock, &mts);
+                mach_port_deallocate(mach_task_self(), cclock);
+                startwork.tv_sec = mts.tv_sec;
+                startwork.tv_nsec = mts.tv_nsec;
+
+                #else
                clock_gettime(CLOCK_REALTIME,&startwork);
-               nanosleep(&twork,NULL);         //now process is working        
+                #endif
+
+               nanosleep(&twork,NULL);         //now process is working
+                #ifdef __MACH__ 
+                // OS X does not have clock_gettime, use clock_get_time
+                // clock_serv_t cclock;
+                // mach_timespec_t mts;
+                host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+                clock_get_time(cclock, &mts);
+                mach_port_deallocate(mach_task_self(), cclock);
+                endwork.tv_sec = mts.tv_sec;
+                endwork.tv_nsec = mts.tv_nsec;
+
+                #else
                clock_gettime(CLOCK_REALTIME,&endwork);
+                #endif
                workingtime=timediff(&endwork,&startwork);
 
-               if (limit<1) {
+               // if (limit<1) {
+                // printf("Checking %f vs %f\n", pcpu, limit);
+                if (pcpu > limit)
+                {
+                        // printf("Stop\n");
                        //stop process, it has worked enough
                        if (kill(pid,SIGSTOP)!=0) {
                                fprintf(stderr,"Process %d dead!\n",pid);
diff --git a/cpulimit.orig b/cpulimit.orig
new file mode 100644 (file)
index 0000000..84012b8
--- /dev/null
@@ -0,0 +1,863 @@
+/**
+ * This program is licensed under the GNU General Public License,
+ * version 2. A copy of the license can be found in the accompanying
+ * LICENSE file.
+ *
+ **********************************************************************
+ *
+ * Simple program to limit the cpu usage of a process
+ * If you modify this code, send me a copy please
+ *
+ * Author:  Angelo Marletta
+ * Date:    26/06/2005
+ * Version: 1.1
+ *
+ * Modifications and updates by: Jesse Smith
+ * Date: May 4, 2011
+ * Version 1.2
+ *
+ */
+
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <sys/resource.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>    // for compatibility
+
+#ifdef __MACH__
+#include <mach/clock.h>
+#include <mach/mach.h>
+#endif
+
+
+
+//kernel time resolution (inverse of one jiffy interval) in Hertz
+//i don't know how to detect it, then define to the default (not very clean!)
+#define HZ 100
+
+//some useful macro
+#define min(a,b) (a<b?a:b)
+#define max(a,b) (a>b?a:b)
+
+// For platforms without PATH_MAX
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+#define BEST_PRIORITY -10
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef VERSION
+#define VERSION 1.7
+#endif
+
+//pid of the controlled process
+pid_t pid=0;
+pid_t my_pid;     // this process's PID
+
+//executable file name
+char *program_name;
+//verbose mode
+int verbose = FALSE;
+//lazy mode
+int lazy = FALSE;
+// is higher priority nice possible?
+int nice_lim;
+
+// number of CPUs we detected
+int NCPU;
+
+//reverse byte search
+// void *memrchr(const void *s, int c, size_t n);
+
+
+
+//return ta-tb in microseconds (no overflow checks!)
+inline long timediff(const struct timespec *ta,const struct timespec *tb) {
+    unsigned long us = (ta->tv_sec-tb->tv_sec)*1000000 + (ta->tv_nsec/1000 - tb->tv_nsec/1000);
+    return us;
+}
+
+
+
+int Check_Us(pid_t target_pid)
+{
+   pid_t this_pid;
+
+   this_pid = getpid();
+   if (this_pid == target_pid)
+   {
+      printf("We cannot throttle ourselves.\n");
+      exit(7);
+   }
+   return TRUE;
+}
+
+
+int waitforpid(int pid) {
+       //switch to low priority
+       // if (setpriority(PRIO_PROCESS,getpid(),19)!=0) {
+        /*
+        if ( (nice_lim < INT_MAX) && 
+             (setpriority(PRIO_PROCESS, my_pid, 19) != 0) ) {
+               printf("Warning: cannot renice\n");
+       }
+        */
+       int i=0;
+
+       while(1) {
+
+               DIR *dip;
+               struct dirent *dit;
+
+               //open a directory stream to /proc directory
+               if ((dip = opendir("/proc")) == NULL) {
+                       perror("opendir");
+                       return -1;
+               }
+
+               //read in from /proc and seek for process dirs
+               while ((dit = readdir(dip)) != NULL) {
+                       //get pid
+                       if (pid==atoi(dit->d_name)) {
+                               //pid detected
+                                Check_Us(pid);
+                               if (kill(pid,SIGSTOP)==0 &&  kill(pid,SIGCONT)==0) {
+                                       //process is ok!
+                                        if (closedir(dip) == -1) {
+                                           perror("closedir");
+                                           return -1;
+                                        }
+                                       goto done;
+                               }
+                               else {
+                                       fprintf(stderr,"Error: Process %d detected, but you don't have permission to control it\n",pid);
+                               }
+                       }
+               }
+
+               //close the dir stream and check for errors
+               if (closedir(dip) == -1) {
+                       perror("closedir");
+                       return -1;
+               }
+
+               //no suitable target found
+               if (i++==0) {
+                       if (lazy) {
+                               fprintf(stderr,"No process found\n");
+                               exit(2);
+                       }
+                       else {
+                               printf("Warning: no target process found. Waiting for it...\n");
+                       }
+               }
+
+               //sleep for a while
+               sleep(2);
+       }
+
+done:
+       printf("Process %d detected\n",pid);
+       //now set high priority, if possible
+       // if (setpriority(PRIO_PROCESS,getpid(),-20)!=0) {
+        /*
+        if ( (nice_lim < INT_MAX) &&
+             (setpriority(PRIO_PROCESS, my_pid, nice_lim) != 0) ) {
+               printf("Warning: cannot renice.\nTo work better you should run this program as root.\n");
+       }
+        */
+       return 0;
+
+}
+
+//this function periodically scans process list and looks for executable path names
+//it should be executed in a low priority context, since precise timing does not matter
+//if a process is found then its pid is returned
+//process: the name of the wanted process, can be an absolute path name to the executable file
+//         or simply its name
+//return: pid of the found process
+int getpidof(const char *process) {
+
+       //set low priority
+       // if (setpriority(PRIO_PROCESS,getpid(),19)!=0) {
+        /*
+        if ( (nice_lim < INT_MAX) &&
+             (setpriority(PRIO_PROCESS, my_pid, 19) != 0) ) {
+               printf("Warning: cannot renice\n");
+       }
+        */
+       char exelink[20];
+       char exepath[PATH_MAX+1];
+       int pid=0;
+       int i=0;
+
+       while(1) {
+
+               DIR *dip;
+               struct dirent *dit;
+
+               //open a directory stream to /proc directory
+               if ((dip = opendir("/proc")) == NULL) {
+                       perror("opendir");
+                       return -1;
+               }
+
+               //read in from /proc and seek for process dirs
+               while ((dit = readdir(dip)) != NULL) {
+                       //get pid
+                       pid=atoi(dit->d_name);
+                       if (pid>0) {
+                               sprintf(exelink,"/proc/%d/exe",pid);
+                               int size=readlink(exelink,exepath,sizeof(exepath));
+                               if (size>0) {
+                                       int found=0;
+                                       if (process[0]=='/' && strncmp(exepath,process,size)==0 && size==strlen(process)) {
+                                               //process starts with / then it's an absolute path
+                                               found=1;
+                                       }
+                                       else {
+                                               //process is the name of the executable file
+                                               if (strncmp(exepath+size-strlen(process),process,strlen(process))==0) {
+                                                       found=1;
+                                               }
+                                       }
+                                       if (found==1) {
+                                        Check_Us(pid);
+                                               if (kill(pid,SIGSTOP)==0 &&  kill(pid,SIGCONT)==0) {
+                                                       //process is ok!
+                                                        if (closedir(dip) == -1) {
+                                                          perror("closedir");
+                                                          return -1;
+                                                        }
+                                                       goto done;
+                                               }
+                                               else {
+                                                       fprintf(stderr,"Error: Process %d detected, but you don't have permission to control it\n",pid);
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               //close the dir stream and check for errors
+               if (closedir(dip) == -1) {
+                       perror("closedir");
+                       return -1;
+               }
+
+               //no suitable target found
+               if (i++==0) {
+                       if (lazy) {
+                               fprintf(stderr,"No process found\n");
+                               exit(2);
+                       }
+                       else {
+                               printf("Warning: no target process found. Waiting for it...\n");
+                       }
+               }
+
+               //sleep for a while
+               sleep(2);
+       }
+
+done:
+       printf("Process %d detected\n",pid);
+       //now set high priority, if possible
+       // if (setpriority(PRIO_PROCESS,getpid(),-20)!=0) {
+        /*
+        if ( (nice_lim < INT_MAX) &&
+             (setpriority(PRIO_PROCESS, my_pid, nice_lim) != 0) ) {
+               printf("Warning: cannot renice.\nTo work better you should run this program as root.\n");
+       }
+        */
+       return pid;
+
+}
+
+//SIGINT and SIGTERM signal handler
+void quit(int sig) {
+       //let the process continue if it's stopped
+       kill(pid,SIGCONT);
+       printf("Exiting...\n");
+       exit(0);
+}
+
+//get jiffies count from /proc filesystem
+int getjiffies(int pid) {
+       static char stat[20];
+       static char buffer[1024];
+        char *p;
+       sprintf(stat,"/proc/%d/stat",pid);
+       FILE *f=fopen(stat,"r");
+       if (f==NULL) return -1;
+       p = fgets(buffer,sizeof(buffer),f);
+       fclose(f);
+       // char *p=buffer;
+        if (p)
+        {
+         p=memchr(p+1,')',sizeof(buffer)-(p-buffer));
+         int sp=12;
+         while (sp--)
+               p=memchr(p+1,' ',sizeof(buffer)-(p-buffer));
+         //user mode jiffies
+         int utime=atoi(p+1);
+         p=memchr(p+1,' ',sizeof(buffer)-(p-buffer));
+         //kernel mode jiffies
+         int ktime=atoi(p+1);
+         return utime+ktime;
+        }
+        // could not read info
+        return -1;
+}
+
+//process instant photo
+struct process_screenshot {
+       struct timespec when;   //timestamp
+       int jiffies;    //jiffies count of the process
+       int cputime;    //microseconds of work from previous screenshot to current
+};
+
+//extracted process statistics
+struct cpu_usage {
+       float pcpu;
+       float workingrate;
+};
+
+//this function is an autonomous dynamic system
+//it works with static variables (state variables of the system), that keep memory of recent past
+//its aim is to estimate the cpu usage of the process
+//to work properly it should be called in a fixed periodic way
+//perhaps i will put it in a separate thread...
+int compute_cpu_usage(int pid,int last_working_quantum,struct cpu_usage *pusage) {
+       #define MEM_ORDER 10
+       //circular buffer containing last MEM_ORDER process screenshots
+       static struct process_screenshot ps[MEM_ORDER];
+       //the last screenshot recorded in the buffer
+       static int front=-1;
+       //the oldest screenshot recorded in the buffer
+       static int tail=0;
+
+       if (pusage==NULL) {
+               //reinit static variables
+               front=-1;
+               tail=0;
+               return 0;
+       }
+
+       //let's advance front index and save the screenshot
+       front=(front+1)%MEM_ORDER;
+       int j=getjiffies(pid);
+       if (j>=0) ps[front].jiffies=j;
+       else return -1; //error: pid does not exist
+
+        #ifdef __MACH__ 
+        // OS X does not have clock_gettime, use clock_get_time
+        clock_serv_t cclock;
+        mach_timespec_t mts;
+        host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+        clock_get_time(cclock, &mts);
+        mach_port_deallocate(mach_task_self(), cclock);
+        ps[front].when.tv_sec = mts.tv_sec;
+        ps[front].when.tv_nsec = mts.tv_nsec;
+
+        #else
+        // Linux and BSD can use real time
+       clock_gettime(CLOCK_REALTIME,&(ps[front].when));
+       ps[front].cputime=last_working_quantum;
+        #endif
+       //buffer actual size is: (front-tail+MEM_ORDER)%MEM_ORDER+1
+       int size=(front-tail+MEM_ORDER)%MEM_ORDER+1;
+
+       if (size==1) {
+               //not enough samples taken (it's the first one!), return -1
+               pusage->pcpu=-1;
+               pusage->workingrate=1;
+               return 0;
+       }
+       else {
+               //now we can calculate cpu usage, interval dt and dtwork are expressed in microseconds
+               long dt=timediff(&(ps[front].when),&(ps[tail].when));
+               long dtwork=0;
+               int i=(tail+1)%MEM_ORDER;
+               int max=(front+1)%MEM_ORDER;
+               do {
+                       dtwork+=ps[i].cputime;
+                       i=(i+1)%MEM_ORDER;
+               } while (i!=max);
+               int used=ps[front].jiffies-ps[tail].jiffies;
+               float usage=(used*1000000.0/HZ)/dtwork;
+               pusage->workingrate=1.0*dtwork/dt;
+               pusage->pcpu=usage*pusage->workingrate;
+               if (size==MEM_ORDER)
+                       tail=(tail+1)%MEM_ORDER;
+               return 0;
+       }
+       #undef MEM_ORDER
+}
+
+void print_caption() {
+       printf("\n%%CPU\twork quantum\tsleep quantum\tactive rate\n");
+}
+
+
+void increase_priority()
+{
+        //find the best available nice value
+        int old_priority = getpriority(PRIO_PROCESS, 0);
+        int priority = old_priority;
+        while ( (setpriority(PRIO_PROCESS, 0, priority-1) == 0) &&
+                (priority > BEST_PRIORITY) )
+        {
+                priority--;
+        }
+        if (priority != old_priority) {
+                if (verbose) printf("Priority changed to %d\n", priority);
+        }
+        else {
+                if (verbose) printf("Warning: Cannot change priority. Run as root or renice for best results.\n");
+        }
+
+
+}
+
+
+
+void print_usage(FILE *stream,int exit_code) {
+        fprintf(stream, "CPUlimit version %1.1f\n", VERSION);
+       fprintf(stream, "Usage: %s TARGET [OPTIONS...]\n",program_name);
+       fprintf(stream, "   TARGET must be exactly one of these:\n");
+       fprintf(stream, "      -p, --pid=N        pid of the process\n");
+       fprintf(stream, "      -e, --exe=FILE     name of the executable program file\n");
+        fprintf(stream, "                         The -e option only works when\n");
+        fprintf(stream, "                         cpulimit is run with admin rights.\n");
+       fprintf(stream, "      -P, --path=PATH    absolute path name of the\n");
+        fprintf(stream, "                         executable program file\n");
+       fprintf(stream, "   OPTIONS\n");
+        fprintf(stream, "      -b  --background   run in background\n");
+        fprintf(stream, "      -c  --cpu=N        override the detection of CPUs on the machine.\n");
+       fprintf(stream, "      -l, --limit=N      percentage of cpu allowed from 1 up.\n");
+        fprintf(stream, "                         Usually 1 - %d00, but can be higher\n", NCPU);
+        fprintf(stream, "                         on multi-core CPUs (mandatory)\n");
+       fprintf(stream, "      -v, --verbose      show control statistics\n");
+       fprintf(stream, "      -z, --lazy         exit if there is no suitable target process,\n");
+        fprintf(stream, "                         or if it dies\n");
+       fprintf(stream, "      -h, --help         display this help and exit\n");
+       exit(exit_code);
+}
+
+
+
+// Get the number of CPU cores on this machine.
+int get_ncpu()
+{
+        int ncpu = 1;
+#ifdef _SC_NPROCESSORS_ONLN
+        ncpu = sysconf(_SC_NPROCESSORS_ONLN);
+#endif
+        return ncpu;
+}
+
+
+
+
+int main(int argc, char **argv) {
+
+       //get program name
+       // char *p=(char*)memrchr(argv[0],(unsigned int)'/',strlen(argv[0]));
+       // program_name = p==NULL?argv[0]:(p+1);
+        program_name = argv[0];
+        int run_in_background = FALSE;
+       //parse arguments
+       int next_option;
+       /* A string listing valid short options letters. */
+       const char* short_options="p:e:P:l:c:bvzh";
+       /* An array describing valid long options. */
+       const struct option long_options[] = {
+               { "pid", required_argument, NULL, 'p' },
+               { "exe", required_argument, NULL, 'e' },
+               { "path", required_argument, NULL, 'P' },
+               { "limit", required_argument, NULL, 'l' },
+                { "background", no_argument, NULL, 'b' },
+               { "verbose", no_argument, NULL, 'v' },
+               { "lazy", no_argument, NULL, 'z' },
+               { "help", no_argument, NULL, 'h' },
+                { "cpu", required_argument, NULL, 'c'},
+               { NULL, 0, NULL, 0 }
+       };
+       //argument variables
+       const char *exe=NULL;
+       const char *path=NULL;
+       int perclimit=0;
+       int pid_ok = FALSE;
+       int process_ok = FALSE;
+       int limit_ok = FALSE;
+        int last_known_argument = 0;
+        // struct rlimit maxlimit;
+
+        NCPU = get_ncpu();
+
+       do {
+               next_option = getopt_long (argc, argv, short_options,long_options, NULL);
+               switch(next_option) {
+                        case 'b':
+                                run_in_background = TRUE;
+                                last_known_argument++;
+                                break;
+                       case 'p':
+                               pid=atoi(optarg);
+                               pid_ok = TRUE;
+                                lazy = TRUE;
+                                last_known_argument += 2;
+                               break;
+                       case 'e':
+                               exe=optarg;
+                               process_ok = TRUE;
+                                last_known_argument += 2;
+                               break;
+                       case 'P':
+                               path=optarg;
+                               process_ok = TRUE;
+                                last_known_argument += 2;
+                               break;
+                       case 'l':
+                               perclimit=atoi(optarg);
+                               limit_ok = TRUE;
+                                last_known_argument += 2;
+                               break;
+                        case 'c':
+                                NCPU = atoi(optarg);
+                                last_known_argument += 2;
+                                break;
+                       case 'v':
+                               verbose = TRUE;
+                                last_known_argument++;
+                               break;
+                       case 'z':
+                               lazy = TRUE;
+                                last_known_argument++;
+                               break;
+                       case 'h':
+                               print_usage (stdout, 1);
+                                last_known_argument++;
+                               break;
+                       case '?':
+                               print_usage (stderr, 1);
+                                last_known_argument++;
+                               break;
+                       case -1:
+                               break;
+                       // default:
+                       //      abort();
+               }
+       } while(next_option != -1);
+
+
+        if (last_known_argument + 1 < argc)
+        {
+           last_known_argument++;
+           pid_t forked_pid;
+           // try to launch remaining arguments
+           if (verbose)
+           {
+               int index = last_known_argument;
+               printf("Launching %s ", argv[index]);
+               for (index = last_known_argument + 1; index < argc; index++)
+                    printf("%s", argv[index]);
+               printf(" with limit %d\n", perclimit);
+           }
+           forked_pid = fork();
+           if (forked_pid == -1)  // error
+           {
+               printf("Failed to launch specified process.\n");
+               exit(1);
+           }
+           else if (forked_pid == 0)   // target child
+           {
+              execvp(argv[last_known_argument],
+                     &(argv[last_known_argument]) );
+              exit(2);
+           }
+           else     // parent who will now fork the throttler
+           {
+              pid_t limit_pid;
+              limit_pid = fork();
+              if (limit_pid == 0)   // child
+              {
+                 pid = forked_pid;    // the first child
+                 lazy = TRUE;
+                 pid_ok = TRUE;
+                 if (verbose)
+                   printf("Throttling process %d\n", (int) pid);
+              }
+              else    // parent
+                exit(0);
+           }
+
+           /*
+           else if (forked_pid == 0)   // child
+           {
+               lazy = TRUE;
+               pid_ok = TRUE;
+               pid = getppid();
+               if (verbose)
+                  printf("Throttling process %d\n", (int) pid);
+           }
+           else // parent
+           {
+               execvp(argv[last_known_argument], 
+                      &(argv[last_known_argument]));
+               
+               // we should never return  
+               exit(2);
+           }
+           */
+           
+        }      // end of launching child process
+
+       if (!process_ok && !pid_ok) {
+               fprintf(stderr,"Error: You must specify a target process\n");
+               print_usage (stderr, 1);
+               exit(1);
+       }
+       if ((exe!=NULL && path!=NULL) || (pid_ok && (exe!=NULL || path!=NULL))) {
+               fprintf(stderr,"Error: You must specify exactly one target process\n");
+               print_usage (stderr, 1);
+               exit(1);
+       }
+       if (!limit_ok) {
+               fprintf(stderr,"Error: You must specify a cpu limit\n");
+               print_usage (stderr, 1);
+               exit(1);
+       }
+       float limit=perclimit/100.0;
+       if ( (limit <= 0.00) || (limit > NCPU) )
+        {
+               fprintf(stderr,"Error: limit must be in the range of 1 to %d00\n", NCPU);
+               print_usage (stderr, 1);
+               exit(1);
+       }
+
+        // check to see if we should fork
+        if (run_in_background)
+        {
+             pid_t process_id;
+             process_id = fork();
+             if (! process_id)
+                exit(0);
+             else
+             {
+                setsid();
+                process_id = fork();
+                if (process_id)
+                  exit(0);
+             }
+        }
+
+       //parameters are all ok!
+       signal(SIGINT,quit);
+       signal(SIGTERM,quit);
+
+        my_pid = getpid();
+        if (verbose)
+           printf("%d CPUs detected.\n", NCPU);
+
+        increase_priority();
+/*
+Instead of all this big block of code to detect and change
+priority settings, let us just use the increase_priority()
+function. It is a little more simple and takes a more
+gradual approach, rather than "all or nothing".
+-- Jesse
+
+       if (setpriority(PRIO_PROCESS, my_pid,-20)!=0) {
+       //if that failed, check if we have a limit 
+        // by how much we can raise the priority
+#ifdef RLIMIT_NICE 
+//check if non-root can even make changes 
+// (ifdef because it's only available in linux >= 2.6.13)
+               nice_lim=getpriority(PRIO_PROCESS, my_pid);
+               getrlimit(RLIMIT_NICE, &maxlimit);
+
+//if we can do better then current
+               if( (20 - (signed)maxlimit.rlim_cur) < nice_lim &&  
+                   setpriority(PRIO_PROCESS, my_pid,
+                    20 - (signed)maxlimit.rlim_cur)==0 //and it actually works
+                 ) {
+
+                       //if we can do better, but not by much, warn about it
+                       if( (nice_lim - (20 - (signed)maxlimit.rlim_cur)) < 9) 
+                        {
+                       printf("Warning, can only increase priority by %d.\n",                                nice_lim - (20 - (signed)maxlimit.rlim_cur));
+                       }
+                        //our new limit
+                       nice_lim = 20 - (signed)maxlimit.rlim_cur; 
+
+               } else 
+// otherwise don't try to change priority. 
+// The below will also run if it's not possible 
+// for non-root to change priority
+#endif
+               {
+                       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");
+                       nice_lim=INT_MAX;
+               }
+       } else {
+               nice_lim=-20;
+       }
+*/
+
+
+       //time quantum in microseconds. it's splitted in a working period and a sleeping one
+       int period=100000;
+       struct timespec twork,tsleep;   //working and sleeping intervals
+       memset(&twork,0,sizeof(struct timespec));
+       memset(&tsleep,0,sizeof(struct timespec));
+
+wait_for_process:
+
+       //look for the target process..or wait for it
+       if (exe!=NULL)
+               pid=getpidof(exe);
+       else if (path!=NULL)
+               pid=getpidof(path);
+       else 
+               waitforpid(pid);
+
+       
+       //process detected...let's play
+
+       //init compute_cpu_usage internal stuff
+       compute_cpu_usage(0,0,NULL);
+       //main loop counter
+       int i=0;
+
+       struct timespec startwork,endwork;
+       long workingtime=0;             //last working time in microseconds
+
+       if (verbose) print_caption();
+
+       float pcpu_avg=0;
+
+       //here we should already have high priority, for time precision
+       while(1) {
+
+               //estimate how much the controlled process is using the cpu in its working interval
+               struct cpu_usage cu;
+               if (compute_cpu_usage(pid,workingtime,&cu)==-1) {
+                       fprintf(stderr,"Process %d dead!\n",pid);
+                       if (lazy) exit(2);
+                       //wait until our process appears
+                       goto wait_for_process;          
+               }
+
+               //cpu actual usage of process (range 0-1)
+               float pcpu=cu.pcpu;
+               //rate at which we are keeping active the process (range 0-1)
+               float workingrate=cu.workingrate;
+
+               //adjust work and sleep time slices
+               if (pcpu>0) {
+                       twork.tv_nsec=min(period*limit*1000/pcpu*workingrate,period*1000);
+               }
+               else if (pcpu==0) {
+                       twork.tv_nsec=period*1000;
+               }
+               else if (pcpu==-1) {
+                       //not yet a valid idea of cpu usage
+                       pcpu=limit;
+                       workingrate=limit;
+                       twork.tv_nsec=min(period*limit*1000,period*1000);
+               }
+               tsleep.tv_nsec=period*1000-twork.tv_nsec;
+
+               //update average usage
+               pcpu_avg=(pcpu_avg*i+pcpu)/(i+1);
+
+               if (verbose && i%10==0 && i>0) {
+                       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);
+                        if (i%200 == 0)
+                           print_caption();
+               }
+
+               // if (limit<1 && limit>0) {
+                // printf("Comparing %f to %f\n", pcpu, limit);
+                if (pcpu < limit)
+                {
+                        // printf("Continue\n");
+                       //resume process
+                       if (kill(pid,SIGCONT)!=0) {
+                               fprintf(stderr,"Process %d dead!\n",pid);
+                               if (lazy) exit(2);
+                               //wait until our process appears
+                               goto wait_for_process;
+                       }
+               }
+
+                #ifdef __MACH__ 
+                // OS X does not have clock_gettime, use clock_get_time
+                clock_serv_t cclock;
+                mach_timespec_t mts;
+                host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+                clock_get_time(cclock, &mts);
+                mach_port_deallocate(mach_task_self(), cclock);
+                startwork.tv_sec = mts.tv_sec;
+                startwork.tv_nsec = mts.tv_nsec;
+
+                #else
+               clock_gettime(CLOCK_REALTIME,&startwork);
+                #endif
+
+               nanosleep(&twork,NULL);         //now process is working
+                #ifdef __MACH__ 
+                // OS X does not have clock_gettime, use clock_get_time
+                // clock_serv_t cclock;
+                // mach_timespec_t mts;
+                host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+                clock_get_time(cclock, &mts);
+                mach_port_deallocate(mach_task_self(), cclock);
+                endwork.tv_sec = mts.tv_sec;
+                endwork.tv_nsec = mts.tv_nsec;
+
+                #else
+               clock_gettime(CLOCK_REALTIME,&endwork);
+                #endif
+               workingtime=timediff(&endwork,&startwork);
+
+               // if (limit<1) {
+                // printf("Checking %f vs %f\n", pcpu, limit);
+                if (pcpu > limit)
+                {
+                        // printf("Stop\n");
+                       //stop process, it has worked enough
+                       if (kill(pid,SIGSTOP)!=0) {
+                               fprintf(stderr,"Process %d dead!\n",pid);
+                               if (lazy) exit(2);
+                               //wait until our process appears
+                               goto wait_for_process;
+                       }
+                       nanosleep(&tsleep,NULL);        //now process is sleeping
+               }
+               i++;
+       }
+
+   return 0;
+}
diff --git a/test/Makefile b/test/Makefile
new file mode 100644 (file)
index 0000000..11f7e2a
--- /dev/null
@@ -0,0 +1,13 @@
+CC?=gcc
+CFLAGS?=-Wall -O2
+TARGETS=busy
+LIBS?=-lpthread
+
+all::  $(TARGETS)
+
+busy:  busy.c $(LIBS)
+       $(CC) -o busy busy.c $(LIBS) $(CFLAGS)
+
+clean:
+       rm -f *~ *.o $(TARGETS)
+
diff --git a/test/busy.c b/test/busy.c
new file mode 100644 (file)
index 0000000..b0484e6
--- /dev/null
@@ -0,0 +1,31 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+void *loop()
+{
+       while(1);
+}
+
+int main(int argc, char **argv) {
+
+       int i = 0;
+       int num_threads = 1;
+       if (argc == 2) num_threads = atoi(argv[1]);
+        printf("Process PID: %d\n", getpid() );
+       for (i=0; i<num_threads; i++)
+       {
+               pthread_t thread;
+               int ret;
+               if ((ret = pthread_create(&thread, NULL, loop, NULL)) != 0)
+               {
+                       printf("pthread_create() failed. Error code %d\n", ret);
+                       exit(1);
+               }
+       }
+       printf("Press ENTER to exit...");
+       getchar();
+       return 0;
+}