int cpulimit_entry(int verbose_setting, int lazy_setting, int perclimit, int pid, char *exe, char *path) { verbose = verbose_setting; lazy = lazy_setting; //used to determine what type of mode to use int pid_ok = 0; int process_ok = 0; if (pid > 0) pid_ok = 1; if (exe != NULL || path != NULL) process_ok = 1; if (!process_ok && !pid_ok) { daemon_log(LOG_ERR, "cpulimit: you must specify a target process."); return 1; } if ((exe != NULL && path != NULL) || (pid_ok && (exe != NULL || path != NULL))) { daemon_log(LOG_ERR, "cpulimit: you must specify exactly one target process."); return 1; } float limit = perclimit / 100.0; if (limit < 0 || limit > 1) { daemon_log(LOG_ERR, "cpulimit: limit must be in the range 0-100."); return 1; } //parameters are all ok! signal(SIGINT, quit); signal(SIGTERM, quit); //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) { if ((pid = getpidof(exe)) == -1) { return 1; } } else if (path != NULL) { if ((pid = getpidof(path)) == -1) { return 1; } } else { if (waitforpid(pid) == -1) { return 1; } } //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 (!sigint) { //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) { daemon_log(LOG_ERR, "process %d dead!", pid); if (lazy) return 1; //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 (limit < 1 && limit > 0) { //resume process if (kill(pid, SIGCONT) != 0) { daemon_log(LOG_ERR, "process %d dead!", pid); if (lazy) return 1; //wait until our process appears goto wait_for_process; } } clock_gettime(CLOCK_REALTIME, &startwork); nanosleep(&twork, NULL); //now process is working clock_gettime(CLOCK_REALTIME, &endwork); workingtime = timediff(&endwork, &startwork); if (limit < 1) { //stop process, it has worked enough if (kill(pid, SIGSTOP) != 0) { daemon_log(LOG_ERR, "process %d dead!", pid); if (lazy) return 1; //wait until our process appears goto wait_for_process; } nanosleep(&tsleep, NULL); //now process is sleeping } i++; } // Start the process, if it is stopped kill(pid, SIGCONT); return 0; }
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:bqkrvzh"; /* 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' }, { "quiet", no_argument, NULL, 'q' }, { "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 'q': quiet = 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) { if (!quiet) 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) { if (!quiet) 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); if (!quiet) 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) { if (!quiet) 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; }