cpulimit-2.0/0000755000175000017500000000000012264617163012215 5ustar jessejessecpulimit-2.0/CHANGELOG0000664000175000017500000001071312264616440013430 0ustar jessejesse=========== Changes in 2.0 ================ * Added the -- flag to make sure child processes run with command line flags would not confuse cpulimit. * Corrected output of child process name in verbose mode. =========== Changes in 1.9 ================ * Swapped out __MACH__ pre-compiler flag for __APPLE__ to avoid conflicts with GNU Hurd. * Added --kill (-k) and --restore (-r) flags to allow target processes to be killed and restored rather than simply throttled. * Made sure version number is accepted from Makefile. =========== 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. * 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. * Made sure we define a reasonable buffer size if PATH_MAX is not already defined. Fixes building on Hurd. ======== Changes in 1.5 ============ * Updated man page and cpulimit's "--help" page to reflect support for multi-core CPUs. * Added example of using cpulimit in a shell in the man page and the README file. * The Makefile now uses the default compiler on the system, "cc", and tries to fall back on gcc if no other compiler is found. ======== Changes in 1.4 ============ * We can now accept limits of 100% or higher. Useful for multi-core systems. * Perform sanity check when getting jiffies. Should prevent memory errors if we cannot open proc data. * Added copyright to README. ========== Changes in 1.3 ============ * Updated license information in cpulimit.c and README file * The -b flag is now shown under options instead of targets in the help text. * Include man page from Debian with updates. ========= Changes in 1.2 =========== * Applied Debian patch for checking to see if and how much we can adjust our own process priority. * Added LICENSE file so there wouldn't be any confusion about what license CPUlimit uses. * Applied Debian's patch for long options to avoid segfault. * Applied Debian's Makefile patch. * Added Debian patch to avoid opendir leaks. * Added -b command line parameter to make CPUlimit run in the background, returning control the the user's terminal. * When cpulimit is launched with one PID to track once that process no longer exists, CPUlimit will exit. Same behaviour as though the lazy flag was set. * Ported CPUlimit to FreeBSD ======= cpulimit-1.1 released ============ * Fixed a segmentation fault if controlled process exited in particular circumstances * Better CPU usage estimate * Fixed a <0 %CPU usage reporting in rare cases * Replaced MAX_PATH_SIZE with PATH_MAX already defined in * Command line arguments now available * Now is possible to specify target process by pid cpulimit-2.0/test/0000775000175000017500000000000012025354147013171 5ustar jessejessecpulimit-2.0/test/Makefile0000664000175000017500000000031212046776236014637 0ustar jessejesseCC?=gcc CFLAGS?=-Wall -O2 TARGETS=busy LIBS?=-lpthread all:: $(TARGETS) busy: busy.c $(LIBS) $(CC) -o busy busy.c $(LIBS) $(CFLAGS) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) clean: rm -f *~ *.o $(TARGETS) cpulimit-2.0/test/busy.c0000664000175000017500000000106512046776236014333 0ustar jessejesse#include #include #include #include #include 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 Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. cpulimit-2.0/cpulimit.c0000664000175000017500000007060012264616346014216 0ustar jessejesse/** * 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 * Date: Jan 29, 2013 * Version 1.2 and newer * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for compatibility #ifdef __APPLE__ #include #include #endif #ifdef FREEBSD #include #include #include #include #include #include #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) (ab?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 2.0 #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); } #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]; 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; } #endif //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 __APPLE__ // 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...] [-- PROGRAM]\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, " -k, --kill kill processes going over their limit\n"); fprintf(stream, " instead of just throttling them.\n"); fprintf(stream, " -r, --restore Restore processes after they have\n"); fprintf(stream, " been killed. Works with the -k flag.\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, " -- This is the final CPUlimit option. All following\n"); fprintf(stream, " options are for another program we will launch.\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:bkrvzh"; /* 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; int kill_process = FALSE; // kill process instead of stopping it int restore_process = FALSE; // restore killed process // struct rlimit maxlimit; NCPU = get_ncpu(); opterr = 0; // avoid unwanted error messages for unknown parameters 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); if (pid) // valid PID { 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 'k': kill_process = TRUE; last_known_argument++; break; case 'r': restore_process = TRUE; last_known_argument++; 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 'o': last_known_argument++; next_option = -1; break; case '?': print_usage (stderr, 1); last_known_argument++; break; case -1: break; // default: // abort(); } } while(next_option != -1); // try to launch a program passed on the command line // But only if we do not already have a PID to watch if ( (last_known_argument + 1 < argc) && (pid_ok == FALSE) ) { last_known_argument++; // if we stopped on "--" jump to the next parameter if ( (last_known_argument + 1 < argc) && (! strcmp(argv[last_known_argument], "--") ) ) 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; // if we are planning to kill a process, give it // a running head start to avoid death at start-up if (kill_process) sleep(5); 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 __APPLE_ // 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 __APPLE__ // 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) { // When over our limit we may run into // situations where we want to kill // the offending process, then restart it if (kill_process) { kill(pid, SIGKILL); fprintf(stderr, "Process %d killed.\n", pid); if ( (lazy) && (! restore_process) ) exit(2); // restart killed process if (restore_process) { pid_t new_process; new_process = fork(); if (new_process == -1) { fprintf(stderr, "Failed to restore killed process.\n"); } else if (new_process == 0) { // child which becomes new process if (verbose) printf("Relaunching %s\n", argv[last_known_argument]); execvp(argv[last_known_argument], &(argv[last_known_argument]) ); } else // parent { // we need to track new process pid = new_process; // avoid killing child process sleep(5); } } } // do not kll process, just throttle it else { // 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 } // end of throttle process } // end of process using too much CPU i++; } return 0; } cpulimit-2.0/TODO0000664000175000017500000000013712264304671012705 0ustar jessejesse- light and scalable algorithm for subprocesses detection and limitation - Put source in svn cpulimit-2.0/Makefile0000664000175000017500000000146412264301004013644 0ustar jessejesseVERSION?=2.0 PREFIX?=/usr CFLAGS?=-Wall -O2 -DVERSION=$(VERSION) CC?=gcc all: cpulimit osx: $(CC) -o cpulimit cpulimit.c -D__APPLE__ $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) minix: $(CC) -o cpulimit cpulimit.c $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) freebsd: $(CC) -o cpulimit cpulimit.c -lrt -DFREEBSD $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) cpulimit: cpulimit.c $(CC) -o cpulimit cpulimit.c -lrt -DLINUX $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) tests: $(MAKE) -C test install: cpulimit mkdir -p ${PREFIX}/bin mkdir -p ${PREFIX}/share/man/man1 cp cpulimit ${PREFIX}/bin cp cpulimit.1.gz ${PREFIX}/share/man/man1 deinstall: rm -f ${PREFIX}/bin/cpulimit rm -f ${PREFIX}/share/man/man1/cpulimit.1.gz clean: rm -f *~ cpulimit $(MAKE) -C test clean tarball: clean cd .. && tar czf cpulimit-$(VERSION).tar.gz cpulimit-$(VERSION) cpulimit-2.0/cpulimit.1.gz0000664000175000017500000000316412264617052014547 0ustar jessejesse*Rcpulimit.1Vo6]-X$m芹mxHb#vf(H@Rvܿ~(ɒѥ-0h$;ƳKz;]ftpr@Z)ӢT<<zxzI79B>G~%J'֤̂ҹ`6f@ߝOގ(0\6y<%H}%CTɗQ(L'.EEASxetLCra_T}s!0vgSU7xrE&jG͉ szru>Ε9ߚVb-X P5PN1 2B䂠ްÓ#TGp_ _tt<H@6+L>2O⬫>B)c)%[VrFQ^Qr2AFuMgѓtrOOFz$11nEt=A Qz_" 4T4!!d ء:KQt*MjeR T؛F5de^t?蜕 ȾhEPb͆=c+Kp?3^ҵV_-S==h󌵰LAɚe=jY|ÎF do|.֚M{lS0)Z(lciխm<ţ &%hop"ۈ-FHx\fFN-!͎)(Za(/S(hoV {PS0J0ඊ7 -!,Z laP%ʕ˰>Yf#h7rήSJ[p6Za)5YZ"[\0o#C]  +<Ҹ \b*R kk沐(tSA]x8 %6/2ًX r+쬔P}UesU39,e`J':(F=x>:dj}r0Hʥ2QB.gg?QYs1^R+NTNxjڋgf Ǝ !nm`_+PQ]CMsU-ӍAu%]˞X=.N1·ggQ$U[u9KhP}[+=cq[ii-GQqlvf] ]Ek/qf~x7VDŎ(x@lX^:Ԙޅr8زK0 ?NU"~m{:SJ Copyright 2011-2014, Jesse Smith Where to get LimitCPU ========================== The LimitCPU program can be aquired from http://limitcpu.sourceforge.net 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-2.0.tar.gz Then we run the makefile. cd cpulimit-2.0 make This should produce the executable file "cpulimit". If you would like to install the program to make it available system-wide, run make install Later should you wish to remove the program from your system, run the following command from the limitcpu directory 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 ========================== The LimitCPU program is generally used to throttle the CPU usage of one process. This can be done with the following command where 12345 is the process ID number of a running program and 25 is the maximum percentage of the CPU we are willing to give that program cpulimit -p 12345 -l 25 The above example will cause LimitCPU to keep an eye on the process with ID number 12345 until the program exits. Should we wish to run LimitCPU in the background we can use cpulimit -p 12345 -l 25 -b We can also limit running processes based on their name instead of their process ID, as in this example: cpulimit --exe /usr/bin/bigexe --limit 50 The above example will keep an eye on "bigexe" and, if the application quits and another program called "bigexe" is run, LimitCPU will monitor the new process too. Should we wish to only track the first program and then exit, we can use cpulimit --exe /usr/bin/bigexe --limit 50 -z The "-z" flag tells LimitCPU to stop running once its target is no longer running itself. The following example is useful for scripts where we want to launch a program and then immediately throttle it without knowing its process ID. The "$!" symbol refers to the last program run and the -b flag tells cpulimit to run in the background, returning control to the shell. /usr/bin/someapp & 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 Sometimes we wish to launch other programs and pass them parameters. To avoid confusing cpulimit as to which parameters are for it and which ones are for the child process, we can use the -- flag. For example, the following command launches Firefox in Private Browsing mode. To make sure the "-p" in Firefox's "-private" option does not confuse cpulimit, we use the -- flag. cpulimit -l 25 -- firefox -private In some special cases it may be desirable to kill a program instead of simple throttling its CPU usage. In these cases the -k flag can be used. For example, if we want to run a program called "yellow" and kill it when it goes over 50% CPU usage run the following: cpulimit -l 50 -k yellow The -k flag will terminate a process going over its limit, but what if we want to bring back the killed process to let it run again? For that purpose we have the -r flag. The -r flag causes a killed process to be restored. cpulimit -l 50 -k -r yellow The restore flag (-r) only works when the process to launch has been launched by cpulimit. For this reason it is necessary to specify the target program at the end of the command line as shown above. Bugs and Feedback ============================= Should you have comments, questions, or bugs to report, please send an e-mail to jessefrgsmith@yahoo.ca with the word "LimitCPU" in the subject line.