Imported Upstream version 1.7 upstream/1.7
authorgregor herrmann <gregoa@debian.org>
Wed, 6 Jun 2012 23:04:04 +0000 (01:04 +0200)
committergregor herrmann <gregoa@debian.org>
Wed, 6 Jun 2012 23:04:04 +0000 (01:04 +0200)
CHANGELOG
Makefile
README
TODO [new file with mode: 0644]
cpulimit.1.gz
cpulimit.c

index 04f7077..5df3d59 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,34 @@
+============= Changes in 1.7 ===============
+
+* Minor code cleanup.
+
+* Make sure we do not try to throttle our own process.
+
+* Added "tarball" option to the Makefile to assist
+  in packaging. Moved version number to the makefile.
+
+* Added version information to CPUlimit's help screen.
+
+* Detect the number of CPU cores on the machine and
+  cap the % we can limit. 1 CPU means we can
+  limit processes 1-100%, 2 means 1-200%, 4 means 1-400%.
+
+* Removed extra priority changes. We now only bump
+  our priority once, if we have access to do so.
+  Also simplified priority increases so it's flexible
+  rather than "all or nothing".
+
+* Since we now attempt to detect the number of CPUs
+  available, we also give the user the ability to
+  override our guess. The -c and --cpu flags have
+  been added for this purpose.
+
+* Commands can be launched and throttled by appending
+  commands to the end of CPUlimit's argument list. For
+  example:
+  cpulimit -l 25 firefox
+
+
 ======== Changes in 1.6 ============
 
 * Updated copyright notice in README file.
index b696393..4de68b5 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,4 @@
+VERSION?=1.7
 PREFIX?=/usr
 CFLAGS?=-Wall -O2
 CC?=gcc
@@ -19,3 +20,7 @@ deinstall:
 
 clean:
        rm -f *~ cpulimit
+
+tarball: clean
+       cd .. && tar czf cpulimit-$(VERSION).tar.gz cpulimit-$(VERSION)
+       
diff --git a/README b/README
index 9c512eb..1d5ab92 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.5.tar.gz
+tar zxf cpulimit-1.7.tar.gz
 
 Then we run the makefile.
 
-cd cpulimit-1.5
+cd cpulimit-1.7
 make
 
 This should produce the executable file "cpulimit". If you would like
@@ -103,6 +103,27 @@ run in the background, returning control to the shell.
 cpulimit -p $! -l 25 -b
 
 
+Note: As of version 1.7 LimitCPU will attempt to guess the
+number of CPUs available on the machine and limit usage
+accordingly. For example, machines with dual-cores will
+be able to run processes with 1-200% limits. In case the
+automatic detection does not work, users can over-ride the
+number of CPUs LimitCPU thinks are available using the
+-c command line flag. For example
+
+cpulimit -c 2 -p 12345 -l 150
+
+
+Commands can be launched by LimitCPU by appending the command
+to the end of of LimitCPU's argument list. For example, the
+following command will launch Firefox and limit it to 10% CPU
+usage:
+
+cpulimit -l 10 firefox
+
+
+
+
 
 Bugs and Feedback
 =============================
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..19ac6b5
--- /dev/null
+++ b/TODO
@@ -0,0 +1 @@
+- light and scalable algorithm for subprocesses detection and limitation
index d002287..0ad882e 100644 (file)
Binary files a/cpulimit.1.gz and b/cpulimit.1.gz differ
index a72ed0f..9d3d0a1 100644 (file)
@@ -24,6 +24,8 @@
 #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>
@@ -35,6 +37,7 @@
 #include <limits.h>    // for compatibility
 
 
+
 //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
 #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
-int pid=0;
+pid_t pid=0;
+pid_t my_pid;     // this process's PID
+
 //executable file name
 char *program_name;
 //verbose mode
-int verbose=0;
+int verbose = FALSE;
 //lazy mode
-int lazy=0;
+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);
 
@@ -68,14 +89,31 @@ inline long timediff(const struct timespec *ta,const struct timespec *tb) {
     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, getpid(), 19) != 0) ) {
+             (setpriority(PRIO_PROCESS, my_pid, 19) != 0) ) {
                printf("Warning: cannot renice\n");
        }
-
+        */
        int i=0;
 
        while(1) {
@@ -94,6 +132,7 @@ int waitforpid(int pid) {
                        //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) {
@@ -133,10 +172,12 @@ 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, getpid(), nice_lim) != 0) ) {
+             (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;
 
 }
@@ -151,11 +192,12 @@ int getpidof(const char *process) {
 
        //set low priority
        // if (setpriority(PRIO_PROCESS,getpid(),19)!=0) {
+        /*
         if ( (nice_lim < INT_MAX) &&
-             (setpriority(PRIO_PROCESS, getpid(), 19) != 0) ) {
+             (setpriority(PRIO_PROCESS, my_pid, 19) != 0) ) {
                printf("Warning: cannot renice\n");
        }
-
+        */
        char exelink[20];
        char exepath[PATH_MAX+1];
        int pid=0;
@@ -192,6 +234,7 @@ int getpidof(const char *process) {
                                                }
                                        }
                                        if (found==1) {
+                                        Check_Us(pid);
                                                if (kill(pid,SIGSTOP)==0 &&  kill(pid,SIGCONT)==0) {
                                                        //process is ok!
                                                         if (closedir(dip) == -1) {
@@ -233,10 +276,12 @@ 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, getpid(), nice_lim) != 0) ) {
+             (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;
 
 }
@@ -353,7 +398,31 @@ 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");
@@ -364,8 +433,9 @@ void print_usage(FILE *stream,int exit_code) {
         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 - 100, but can be higher\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");
@@ -374,16 +444,31 @@ void print_usage(FILE *stream,int exit_code) {
        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);
-        int run_in_background = 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:bvzh";
+       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' },
@@ -394,59 +479,139 @@ int main(int argc, char **argv) {
                { "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=0;
-       int process_ok=0;
-       int limit_ok=0;
-        struct rlimit maxlimit;
+       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 = 1;
+                                run_in_background = TRUE;
+                                last_known_argument++;
                                 break;
                        case 'p':
                                pid=atoi(optarg);
-                               pid_ok=1;
-                                lazy = 1;
+                               pid_ok = TRUE;
+                                lazy = TRUE;
+                                last_known_argument += 2;
                                break;
                        case 'e':
                                exe=optarg;
-                               process_ok=1;
+                               process_ok = TRUE;
+                                last_known_argument += 2;
                                break;
                        case 'P':
                                path=optarg;
-                               process_ok=1;
+                               process_ok = TRUE;
+                                last_known_argument += 2;
                                break;
                        case 'l':
                                perclimit=atoi(optarg);
-                               limit_ok=1;
+                               limit_ok = TRUE;
+                                last_known_argument += 2;
                                break;
+                        case 'c':
+                                NCPU = atoi(optarg);
+                                last_known_argument += 2;
+                                break;
                        case 'v':
-                               verbose=1;
+                               verbose = TRUE;
+                                last_known_argument++;
                                break;
                        case 'z':
-                               lazy=1;
+                               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();
+                       // 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);
@@ -463,9 +628,9 @@ int main(int argc, char **argv) {
                exit(1);
        }
        float limit=perclimit/100.0;
-       if (limit <= 0.00) // || limit >1) {
+       if ( (limit <= 0.00) || (limit > NCPU) )
         {
-               fprintf(stderr,"Error: limit must be greater than 0\n");
+               fprintf(stderr,"Error: limit must be in the range of 1 to %d00\n", NCPU);
                print_usage (stderr, 1);
                exit(1);
        }
@@ -490,18 +655,30 @@ int main(int argc, char **argv) {
        signal(SIGINT,quit);
        signal(SIGTERM,quit);
 
-       if (setpriority(PRIO_PROCESS,getpid(),-20)!=0) {
+        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,getpid());
+               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,getpid(),
+                   setpriority(PRIO_PROCESS, my_pid,
                     20 - (signed)maxlimit.rlim_cur)==0 //and it actually works
                  ) {
 
@@ -525,9 +702,7 @@ int main(int argc, char **argv) {
        } else {
                nice_lim=-20;
        }
-       //don't bother putting setpriority back down, 
-        // since getpidof and waitforpid twiddle it anyway
-
+*/
 
 
        //time quantum in microseconds. it's splitted in a working period and a sleeping one
@@ -543,9 +718,10 @@ wait_for_process:
                pid=getpidof(exe);
        else if (path!=NULL)
                pid=getpidof(path);
-       else {
+       else 
                waitforpid(pid);
-       }
+
+       
        //process detected...let's play
 
        //init compute_cpu_usage internal stuff
@@ -627,4 +803,5 @@ wait_for_process:
                i++;
        }
 
+   return 0;
 }