From 0a10f2486279ae0d46b51af18d4aa7546343478f Mon Sep 17 00:00:00 2001 From: gregor herrmann Date: Sun, 27 Nov 2016 19:24:43 +0100 Subject: [PATCH] New upstream version 2.4 --- CHANGELOG | 6 + Makefile | 4 +- README | 9 +- TODO | 5 - cpulimit.1 | 3 + cpulimit.c | 324 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 339 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5123e35..1553264 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +========== Changes in 2.4 ================= + +* Introduced ability to watch children of the target + process. This means forks of the process we are throttling + can also be throttled, using the "-m" or "--monitor-forks" flags. + =========== Changes in 2.3 ================ * Applied patch to man page which fixes -s description. diff --git a/Makefile b/Makefile index ecb69f2..64f34f3 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION?=2.3 +VERSION?=2.4 PREFIX?=/usr CFLAGS?=-Wall -O2 -DVERSION=$(VERSION) CC?=gcc @@ -15,7 +15,7 @@ freebsd: $(CC) -o cpulimit cpulimit.c -lrt -DFREEBSD $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) cpulimit: cpulimit.c - $(CC) -o cpulimit cpulimit.c -lrt -DLINUX $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) + $(CC) -o cpulimit cpulimit.c -pthread -lrt -DLINUX $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) tests: $(MAKE) -C test diff --git a/README b/README index 83b6902..5325717 100644 --- a/README +++ b/README @@ -22,7 +22,7 @@ A copy of the license should be included with this program in a file named LICENSE. Copyright 2005, Angelo Marletta -Copyright 2011-2014, Jesse Smith +Copyright 2011-2016, Jesse Smith @@ -181,6 +181,13 @@ instead of the written form of the signal. cpulimit -l 25 -p 1234 --signal=15 +LimitCPU can be used to watch not only one target process, but also +child processes the target spawns. This means new processs the +target creates will also be throttled. By default, LimitCPU +only watches one target process and ignores children, but monitoring +children can be enabled using the "-m" or "--monitor-forks" flag. + +cpulimit -l 25 -p 1234 -m diff --git a/TODO b/TODO index 746dee2..8b13789 100644 --- a/TODO +++ b/TODO @@ -1,6 +1 @@ -- Add "foreground option" so that when spawning a new process cpulimit stays - in the foreground. This will allow for throttling of pograms in scripts. - -- light and scalable algorithm for subprocesses detection and limitation - diff --git a/cpulimit.1 b/cpulimit.1 index 8c6a3f0..d804200 100644 --- a/cpulimit.1 +++ b/cpulimit.1 @@ -37,6 +37,9 @@ Runs in quiet mode, avoids writing update messages to console. \fB\-k\fR, \fB\-\-kill\fR kill target process instead of throttling its CPU usage .TP +\fB\-m\fR, \fB\-\-monitor\-forks\fR +watch and throttle child processes of the target process +.TP \fB\-r\fR, \fB\-\-restore\fR restore a process killed using the \-k flag. .TP diff --git a/cpulimit.c b/cpulimit.c index e248eec..573c35c 100644 --- a/cpulimit.c +++ b/cpulimit.c @@ -56,6 +56,11 @@ #include #endif +#ifdef LINUX +#include +#define PROC_FILENAME 64 +#define LINE_LENGTH 256 +#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!) @@ -80,7 +85,7 @@ #endif #ifndef VERSION -#define VERSION 2.3 +#define VERSION 2.4 #endif //pid of the controlled process @@ -123,6 +128,29 @@ inline long timediff(const struct timespec *ta,const struct timespec *tb) { } +#ifdef LINUX +#include + +typedef struct +{ + pid_t child; // the child of our target process + pid_t monitor; // the LimitCPU fork monitoring the child + void *next; +} CHILD; + +int quitting = FALSE; // Have we receive a quit signal +int monitor_children = FALSE; // are we monitoring children of target + +// Data passed to the monitor thread +typedef struct +{ + int limit; // per cent limit to place on a process + char *this_program; // copy of argv[0] +} PROGRAM_DATA; + +#endif + + int Check_Us(pid_t target_pid) { @@ -325,6 +353,14 @@ done: void quit(int sig) { //let the process continue if we are stopped kill(pid, send_signal); + #ifdef LINUX + if (monitor_children) + { + quitting = TRUE; + printf("Asking children to quit...\n"); + sleep(2); // wait for thread clean-up + } + #endif printf("Exiting...\n"); exit(0); } @@ -334,8 +370,12 @@ void Child_Done(int sig) { pid_t caught_child; caught_child = waitpid(-1, NULL, WNOHANG); - printf("Caught child process: %d\n", (int) caught_child); - printf("%d\n", errno); + if (verbose) + { + printf("Caught child process: %d\n", (int) caught_child); + printf("%d\n", errno); + } + // If this was the one process we were watching, we can quit now. if (caught_child == pid) { printf("Child process is finished, exiting...\n"); @@ -345,7 +385,6 @@ void Child_Done(int sig) #ifdef FREEBSD -//get jiffies count from /proc filesystem int getjiffies(int pid) { kvm_t *my_kernel = NULL; @@ -373,6 +412,7 @@ int getjiffies(int pid) #endif #ifdef LINUX +//get jiffies count from /proc filesystem int getjiffies(int pid) { static char stat[20]; static char buffer[1024]; @@ -514,6 +554,248 @@ void increase_priority() +#ifdef LINUX +// This following functions are for detecting and limiting child +// processes on Linux. + +// This function adds a new child process to our list of child processes. +CHILD *Add_Child(CHILD *all_children, pid_t new_pid) +{ + CHILD *new_child = (CHILD *) calloc(sizeof(CHILD), 1); + CHILD *current; + + if (! new_child) + return all_children; + + new_child->next = NULL; + new_child->child = new_pid; + new_child->monitor = 0; + + if (all_children) + { + current = all_children; + while (current->next) + current = current->next; + current->next = new_child; + return all_children; + } + else // this is the first node + return new_child; +} + + + +// This function removes a child PID node. +CHILD *Remove_Child(CHILD *all_children, pid_t old_pid) +{ + CHILD *current, *previous = NULL; + int found = FALSE; + + current = all_children; + while ( (! found) && (current) ) + { + if (current->child == old_pid) + { + if (previous) + previous->next = current->next; + else + all_children = current->next; + + free(current); + found = TRUE; + } + else + { + previous = current; + current = current->next; + } + } + return all_children; +} + + +// This function cleans up all remaining child nodes. +void Clean_Up_Children(CHILD *all_children) +{ + CHILD *current, *next; + + current = all_children; + while (current) + { + next = current->next; + free(current); + current = next; + } +} + + +// This function searches the linked list for a matching PID. +// It returns NULL if no match is found and a pointer to the +// node is a match is located. +CHILD *Find_Child(CHILD *children, pid_t target) +{ + CHILD *current; + int found = FALSE; + + current = children; + while ( (!found) && (current) ) + { + if (current->child == target) + found = TRUE; + else + current = current->next; + } + return current; +} + + + +// This function returns a list of process IDs of children +// of the given process (PID). It does this by searching the /proc +// file system and looking in the /proc/pid/status file for the PPid field. +// A linked list of child PIDs is returned on success or NULL on failure or +// if no child PIDs are found. +CHILD *Find_Child_PIDs(CHILD *all_children, pid_t parent_pid) +{ + int found = FALSE; + DIR *proc; + struct dirent *proc_entry; + char filename[PROC_FILENAME]; + FILE *status_file; + char *reading_file; + char line[256]; + pid_t new_ppid; + int current_pid; + + proc = opendir("/proc"); + if (! proc) + return all_children; + + proc_entry = readdir(proc); + while (proc_entry) + { + snprintf(filename, PROC_FILENAME, "/proc/%s/status", proc_entry->d_name); + status_file = fopen(filename, "r"); + if (status_file) + { + found = FALSE; + reading_file = fgets(line, LINE_LENGTH, status_file); + while ( (! found) && (reading_file) ) + { + if (! strncmp(line, "PPid:", 5) ) + { + sscanf(&(line[6]), "%d", &new_ppid); + if (new_ppid == parent_pid) + { + sscanf(proc_entry->d_name, "%d", ¤t_pid); + if (! Find_Child(all_children, current_pid) ) + all_children = Add_Child(all_children, current_pid); + } + found = TRUE; + } + else + reading_file = fgets(line, LINE_LENGTH, status_file); + } // done reading status file + + fclose(status_file); + } + proc_entry = readdir(proc); + } // done reading proc file system + closedir(proc); + return all_children; +} + + +// This function (which should probably be called as a thread) monitors the +// system for child processes of the current target process. When a new +// child of the target is located, it is added to the CHILD list. +// New children result in a new fork of this program being spawned to +// monitor the child process and its children. + +void *Monitor_Children(void *all_data) +{ + CHILD *all_children = NULL; + CHILD *current; + PROGRAM_DATA *program_data = (PROGRAM_DATA *) all_data; + + while (! quitting ) + { + // Check for new child processes + all_children = Find_Child_PIDs(all_children, pid); + + // Find any children without monitors and create a monitoring process + // Clean out old processes while we are looking + current = all_children; + while (current) + { + // First see if the child process is still running. If not, + // we can remote its node. + if (current->child) + { + char filename[PROC_FILENAME]; + DIR *child_directory; + snprintf(filename, PROC_FILENAME, "/proc/%d", current->child); + child_directory = opendir(filename); + if (child_directory) + closedir(child_directory); + else + { + if (verbose) + printf("Child process %d done, cleaning up.\n", + (int) current->child); + all_children = Remove_Child(all_children, current->child); + } + } // end of clean up children processes no longer running + + // The child process is still running, but it might not have + // a monitor. Create a new monitoring process. + if ( (current->child) && (! current->monitor) ) + { + pid_t returned_pid; + + if (verbose) + printf("Creating monitoring process for %d\n", + (int) current->child); + + returned_pid = fork(); + if (returned_pid > 0) + { + // parent + current->monitor = returned_pid; + } + else if (returned_pid == 0) + { + // child + char limit_amount[16]; + char process_identifier[16]; + snprintf(limit_amount, 16, "%d", (int) program_data->limit); + snprintf(process_identifier, 16, "%d", current->child); + if (verbose) + printf("Starting monitor with: %s -l %s -p %s -z -m\n", + program_data->this_program, limit_amount, + process_identifier); + execl(program_data->this_program, program_data->this_program, + "-l", limit_amount, "-p", process_identifier, + "-z", "-m", (char *) NULL); + } + + } // end of creating a new monitor + if (verbose) + { + printf("Watching child: %d with %d\n", + (int) current->child, (int) current->monitor); + } + current = current->next; + } + sleep(1); + } // end LimitCPU is still running + pthread_exit(NULL); +} + +#endif // end of monitoring children processes on Linux + + + 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); @@ -531,6 +813,9 @@ void print_usage(FILE *stream,int exit_code) { 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"); + #ifdef LINUX + fprintf(stream, " -m, --monitor-forks Watch children/forks of the target process\n"); + #endif fprintf(stream, " -q, --quiet run in quiet mode (only print errors).\n"); fprintf(stream, " -k, --kill kill processes going over their limit\n"); fprintf(stream, " instead of just throttling them.\n"); @@ -600,7 +885,12 @@ int main(int argc, char **argv) { //parse arguments int next_option; /* A string listing valid short options letters. */ + #ifdef LINUX + const char* short_options="p:e:P:l:c:s:bfqkmrvzh"; + PROGRAM_DATA program_data; + #else const char* short_options="p:e:P:l:c:s:bfqkrvzh"; + #endif /* An array describing valid long options. */ const struct option long_options[] = { { "pid", required_argument, NULL, 'p' }, @@ -615,6 +905,9 @@ int main(int argc, char **argv) { { "help", no_argument, NULL, 'h' }, { "cpu", required_argument, NULL, 'c'}, { "signal", required_argument, NULL, 's'}, + #ifdef LINUX + { "monitor-forks", no_argument, NULL, 'm'}, + #endif { NULL, 0, NULL, 0 } }; //argument variables @@ -686,6 +979,12 @@ int main(int argc, char **argv) { kill_process = TRUE; last_known_argument++; break; + #ifdef LINUX + case 'm': + monitor_children = TRUE; + last_known_argument++; + break; + #endif case 'r': restore_process = TRUE; last_known_argument++; @@ -871,6 +1170,23 @@ wait_for_process: float pcpu_avg=0; + // On Linux we can monitor child processes of the target + #ifdef LINUX + if (monitor_children) + { + pthread_t my_thread; + int thread_status; + if (verbose) + printf("Starting fork monitoring thread...\n"); + program_data.this_program = argv[0]; + program_data.limit = perclimit; + thread_status = pthread_create(&my_thread, NULL, + Monitor_Children, &program_data); + if ( (thread_status) && (verbose) ) + printf("Creating fork monitoring thread failed.\n"); + } + #endif + //here we should already have high priority, for time precision while(1) { -- 2.20.1