From 366177b674808ababd807d124924a64c65ed1aa2 Mon Sep 17 00:00:00 2001 From: Angelo Marletta Date: Wed, 30 May 2012 02:22:41 +0100 Subject: added gitignore. source files moved to src/ --- src/Makefile | 22 +++ src/cpulimit.c | 572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/list.c | 154 +++++++++++++++ src/list.h | 143 ++++++++++++++ src/process.c | 233 +++++++++++++++++++++++ src/process.h | 88 +++++++++ src/procutils.c | 453 ++++++++++++++++++++++++++++++++++++++++++++ src/procutils.h | 94 ++++++++++ 8 files changed, 1759 insertions(+) create mode 100644 src/Makefile create mode 100644 src/cpulimit.c create mode 100644 src/list.c create mode 100644 src/list.h create mode 100644 src/process.c create mode 100644 src/process.h create mode 100644 src/procutils.c create mode 100644 src/procutils.h (limited to 'src') diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..33c632d --- /dev/null +++ b/src/Makefile @@ -0,0 +1,22 @@ +CC?=gcc +CFLAGS?=-Wall -O2 +TARGETS=cpulimit +LIBS=process.o procutils.o list.o + +all:: $(TARGETS) + +cpulimit: cpulimit.c $(LIBS) + $(CC) -o cpulimit cpulimit.c $(LIBS) $(CFLAGS) + +process.o: process.c process.h + $(CC) -c process.c $(CFLAGS) + +procutils.o: procutils.c procutils.h + $(CC) -c procutils.c $(CFLAGS) + +list.o: list.c list.h + $(CC) -c list.c $(CFLAGS) + +clean: + rm -f *~ *.o $(TARGETS) + diff --git a/src/cpulimit.c b/src/cpulimit.c new file mode 100644 index 0000000..0bf3d9c --- /dev/null +++ b/src/cpulimit.c @@ -0,0 +1,572 @@ +/** + * + * cpulimit - a cpu limiter for Linux + * + * Copyright (C) 2005-2008, by: Angelo Marletta + * + * 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. + * + ********************************************************************** + * + * This is a simple program to limit the cpu usage of a process + * If you modify this code, send me a copy please + * + * Date: 30/8/2008 + * Version: 1.2 beta + * Get the latest version at: http://cpulimit.sourceforge.net + * + * Changelog from 1.1: + * - reorganization of the code, splitted in more source files + * - cpu count detection, i.e. if you have 4 cpu, it is possible to limit up to 400% + * - in order to avoid deadlocks, cpulimit now prevents to limit itself + * - option --path eliminated, use --exe instead both for absolute path and file name + * - call setpriority() just once in limit_process() + * - no more segmentation fault when processes exit + * - no more memory corruption when processes exit + * - cpulimit exits if --lazy option is specified and the process terminates + * - target process can be created on-fly given command line + * - light and scalable algorithm for subprocesses detection and limitation + * - mac os support + * - minor enhancements and bugfixes + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "process.h" +#include "procutils.h" +#include "list.h" + +//some useful macro +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +//control time slot in microseconds +//each slot is splitted in a working slice and a sleeping slice +//TODO: make it adaptive, based on the actual system load +#define TIME_SLOT 100000 + +#define MAX_PRIORITY -10 + +/* GLOBAL VARIABLES */ + +//the "family" +struct process_family pf; +//pid of cpulimit +pid_t cpulimit_pid; +//name of this program (maybe cpulimit...) +char *program_name; + +//number of cpu +int NCPU; + +/* CONFIGURATION VARIABLES */ + +//verbose mode +int verbose = 0; +//lazy mode (exits if there is no process) +int lazy = 0; + +static void *memrchr(const void *s, int c, size_t n) +{ + const unsigned char *start = (const unsigned char*)s; + const unsigned char *end = (const unsigned char*)s; + + end+=n-1; + + while(end>=start) { + if(*end==c) + return (void *)end; + else + end--; + } + + return NULL; +} + +//SIGINT and SIGTERM signal handler +static void quit(int sig) +{ + //let all the processes continue if stopped + struct list_node *node = NULL; + for (node=pf.members.first; node!= NULL; node=node->next) { + struct process *p = (struct process*)(node->data); + kill(p->pid, SIGCONT); + process_close(p); + } + //free all the memory + cleanup_process_family(&pf); + //fix ^C little problem + printf("\r"); + fflush(stdout); + exit(0); +} + +//return t1-t2 in microseconds (no overflow checks, so better watch out!) +static inline unsigned long timediff(const struct timeval *t1,const struct timeval *t2) +{ + return (t1->tv_sec - t2->tv_sec) * 1000000 + (t1->tv_usec - t2->tv_usec); +} + +static void print_usage(FILE *stream, int exit_code) +{ + fprintf(stream, "Usage: %s [OPTIONS...] TARGET\n", program_name); + fprintf(stream, " OPTIONS\n"); + fprintf(stream, " -l, --limit=N percentage of cpu allowed from 0 to %d (required)\n", 100*NCPU); + fprintf(stream, " -v, --verbose show control statistics\n"); + fprintf(stream, " -z, --lazy exit if there is no target process, or if it dies\n"); + fprintf(stream, " -i, --ignore-children don't limit children processes\n"); + fprintf(stream, " -h, --help display this help and exit\n"); + fprintf(stream, " TARGET must be exactly one of these:\n"); + fprintf(stream, " -p, --pid=N pid of the process (implies -z)\n"); + fprintf(stream, " -e, --exe=FILE name of the executable program file or path name\n"); + fprintf(stream, " COMMAND [ARGS] run this command and limit it (implies -z)\n"); + fprintf(stream, "\nReport bugs to .\n"); + exit(exit_code); +} + +static 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>MAX_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"); + } +} + +/* Get the number of CPUs */ +static int get_ncpu() { + int ncpu = -1; +#ifdef _SC_NPROCESSORS_ONLN + ncpu = sysconf(_SC_NPROCESSORS_ONLN); +#elif defined __APPLE__ + int mib[2] = {CTL_HW, HW_NCPU}; + size_t len = sizeof(ncpu); + sysctl(mib, 2, &ncpu, &len, NULL, 0); +#endif + return ncpu; +} + +#ifdef __linux__ + +#include + +static int check_proc() +{ + struct statfs mnt; + if (statfs("/proc", &mnt) < 0) + return 0; + if (mnt.f_type!=0x9fa0) + return 0; + return 1; +} +#endif + +void limit_process(pid_t pid, double limit, int ignore_children) +{ + //slice of the slot in which the process is allowed to run + struct timespec twork; + //slice of the slot in which the process is stopped + struct timespec tsleep; + //when the last twork has started + struct timeval startwork; + //when the last twork has finished + struct timeval endwork; + //initialization + memset(&twork, 0, sizeof(struct timespec)); + memset(&tsleep, 0, sizeof(struct timespec)); + memset(&startwork, 0, sizeof(struct timeval)); + memset(&endwork, 0, sizeof(struct timeval)); + //last working time in microseconds + unsigned long workingtime = 0; + //generic list item + struct list_node *node; + //counter + int c = 0; + + //get a better priority + increase_priority(); + + //build the family + create_process_family(&pf, pid); + if (ignore_children) { + //delete any process with a different pid than the father + for (node=pf.members.first; node!=NULL; node=node->next) { + struct process *proc = (struct process*)(node->data); + if (proc->pid != pid) + remove_process_from_family(&pf, proc->pid); + } + } + + if (!ignore_children && verbose) printf("Members in the family owned by %d: %d\n", pf.father, pf.members.count); + + //rate at which we are keeping active the processes (range 0-1) + //1 means that the process are using all the twork slice + double workingrate = -1; + while(1) { + if (!ignore_children && c%10==0) { + //update the process family (checks only for new members) + int new_children = update_process_family(&pf); + if (verbose && new_children) { + printf("%d new children processes detected (", new_children); + int j; + node = pf.members.last; + for (j=0; jdata))->pid); + if (jprevious; + } + printf(")\n"); + } + } + + if (pf.members.count==0) { + if (verbose) printf("No more processes.\n"); + break; + } + + //total cpu actual usage (range 0-1) + //1 means that the processes are using 100% cpu + double pcpu = -1; + + //estimate how much the controlled processes are using the cpu in the working interval + for (node=pf.members.first; node!=NULL; node=node->next) { + struct process *proc = (struct process*)(node->data); + if (proc->is_zombie) { + //process is zombie, remove it from family + fprintf(stderr,"Process %d is zombie!\n", proc->pid); + remove_process_from_family(&pf, proc->pid); + continue; + } + if (process_monitor(proc) != 0) { + //process is dead, remove it from family + if (verbose) fprintf(stderr,"Process %d dead!\n", proc->pid); + remove_process_from_family(&pf, proc->pid); + continue; + } + if (proc->cpu_usage<0) { + continue; + } + if (pcpu<0) pcpu = 0; + pcpu += proc->cpu_usage; + } + + //adjust work and sleep time slices + if (pcpu < 0) { + //it's the 1st cycle, initialize workingrate + pcpu = limit; + workingrate = limit; + twork.tv_nsec = TIME_SLOT*limit*1000; + } + else { + //adjust workingrate + workingrate = MIN(workingrate / pcpu * limit, 1); + twork.tv_nsec = TIME_SLOT*1000*workingrate; + } + tsleep.tv_nsec = TIME_SLOT*1000-twork.tv_nsec; + + if (verbose) { + if (c%200==0) + printf("\n%%CPU\twork quantum\tsleep quantum\tactive rate\n"); + if (c%10==0 && c>0) + printf("%0.2lf%%\t%6ld us\t%6ld us\t%0.2lf%%\n",pcpu*100,twork.tv_nsec/1000,tsleep.tv_nsec/1000,workingrate*100); + } + + //resume processes + for (node=pf.members.first; node!=NULL; node=node->next) { + struct process *proc = (struct process*)(node->data); + if (kill(proc->pid,SIGCONT)!=0) { + //process is dead, remove it from family + if (verbose) fprintf(stderr,"Process %d dead!\n", proc->pid); + remove_process_from_family(&pf, proc->pid); + } + } + + //now processes are free to run (same working slice for all) + gettimeofday(&startwork, NULL); + nanosleep(&twork,NULL); + gettimeofday(&endwork, NULL); + workingtime = timediff(&endwork,&startwork); + + long delay = workingtime-twork.tv_nsec/1000; + if (c>0 && delay>10000) { + //delay is too much! signal to user? + //fprintf(stderr, "%d %ld us\n", c, delay); + } + + if (tsleep.tv_nsec>0) { + //stop only if tsleep>0, instead it's useless + for (node=pf.members.first; node!=NULL; node=node->next) { + struct process *proc = (struct process*)(node->data); + if (kill(proc->pid,SIGSTOP)!=0) { + //process is dead, remove it from family + if (verbose) fprintf(stderr,"Process %d dead!\n", proc->pid); + remove_process_from_family(&pf, proc->pid); + } + } + //now the processes are sleeping + nanosleep(&tsleep,NULL); + } + c++; + } + cleanup_process_family(&pf); +} + +int main(int argc, char **argv) { + //argument variables + const char *exe = NULL; + int perclimit = 0; + int exe_ok = 0; + int pid_ok = 0; + int limit_ok = 0; + pid_t pid = 0; + int ignore_children = 0; + + //get program name + char *p=(char*)memrchr(argv[0],(unsigned int)'/',strlen(argv[0])); + program_name = p==NULL?argv[0]:(p+1); + //get current pid + cpulimit_pid = getpid(); + //get cpu count + NCPU = get_ncpu(); + + //parse arguments + int next_option; + int option_index = 0; + //A string listing valid short options letters + const char* short_options = "+p:e:l:vzih"; + //An array describing valid long options + const struct option long_options[] = { + { "pid", required_argument, NULL, 'p' }, + { "exe", required_argument, NULL, 'e' }, + { "limit", required_argument, NULL, 'l' }, + { "verbose", no_argument, NULL, 'v' }, + { "lazy", no_argument, NULL, 'z' }, + { "ignore-children", no_argument, NULL, 'i' }, + { "help", no_argument, NULL, 'h' }, + { 0, 0, 0, 0 } + }; + + do { + next_option = getopt_long(argc, argv, short_options,long_options, &option_index); + switch(next_option) { + case 'p': + pid = atoi(optarg); + pid_ok = 1; + break; + case 'e': + exe = optarg; + exe_ok = 1; + break; + case 'l': + perclimit = atoi(optarg); + limit_ok = 1; + break; + case 'v': + verbose = 1; + break; + case 'z': + lazy = 1; + break; + case 'i': + ignore_children = 1; + break; + case 'h': + print_usage(stdout, 1); + break; + case '?': + print_usage(stderr, 1); + break; + case -1: + break; + default: + abort(); + } + } while(next_option != -1); + + if (pid_ok && (pid<=1 || pid>=65536)) { + fprintf(stderr,"Error: Invalid value for argument PID\n"); + print_usage(stderr, 1); + exit(1); + } + if (pid!=0) { + lazy = 1; + } + + if (!limit_ok) { + fprintf(stderr,"Error: You must specify a cpu limit percentage\n"); + print_usage(stderr, 1); + exit(1); + } + double limit = perclimit/100.0; + if (limit<0 || limit >NCPU) { + fprintf(stderr,"Error: limit must be in the range 0-%d00\n", NCPU); + print_usage(stderr, 1); + exit(1); + } + + int command_mode = optind 1) { + fprintf(stderr,"Error: You must specify exactly one target process, either by name, pid, or command line\n"); + print_usage(stderr, 1); + exit(1); + } + + //all arguments are ok! + signal(SIGINT, quit); + signal(SIGTERM, quit); + + //print the number of available cpu + if (verbose) printf("%d cpu detected\n", NCPU); + +#ifdef __linux__ + if (!check_proc()) { + fprintf(stderr, "procfs is not mounted!\nAborting\n"); + exit(-2); + } +#endif + + if (command_mode) { + int i; + //executable file + const char *cmd = argv[optind]; + //command line arguments + char **cmd_args = (char**)malloc((argc-optind+1)*sizeof(char*)); + if (cmd_args==NULL) exit(2); + for (i=0; i 0) { + //parent code + int limiter = fork(); + if (limiter < 0) { + exit(EXIT_FAILURE); + } + else if (limiter > 0) { + //parent + int status_process; + int status_limiter; + waitpid(child, &status_process, 0); + waitpid(limiter, &status_limiter, 0); + if (WIFEXITED(status_process)) { + if (verbose) printf("Process %d terminated with exit status %d\n", child, (int)WEXITSTATUS(status_process)); + exit(WEXITSTATUS(status_process)); + } + printf("Process %d terminated abnormally\n", child); + exit(status_process); + } + else { + //limiter code + if (verbose) printf("Limiting process %d\n",child); + limit_process(child, limit, ignore_children); + exit(0); + } + } + else { + //target process code + int ret = execvp(cmd, cmd_args); + //if we are here there was an error, show it + perror("Error"); + exit(ret); + } + } + + while(1) { + //look for the target process..or wait for it + pid_t ret = 0; + if (pid_ok) { + //search by pid + ret = look_for_process_by_pid(pid); + if (ret == 0) { + printf("No process found\n"); + } + else if (ret < 0) { + printf("Process found but you aren't allowed to control it\n"); + } + } + else { + //search by file or path name + ret = look_for_process_by_name(exe); + if (ret == 0) { + printf("No process found\n"); + } + else if (ret < 0) { + printf("Process found but you aren't allowed to control it\n"); + } + else { + pid = ret; + } + } + if (ret > 0) { + if (ret == cpulimit_pid) { + printf("Process %d is cpulimit itself! Aborting to avoid deadlock\n", ret); + exit(1); + } + printf("Process %d found\n", pid); + //control + limit_process(pid, limit, ignore_children); + } + if (lazy) break; + sleep(2); + }; + + exit(0); +} diff --git a/src/list.c b/src/list.c new file mode 100644 index 0000000..49f84ce --- /dev/null +++ b/src/list.c @@ -0,0 +1,154 @@ +/** +* +* cpulimit - a cpu limiter for Linux +* +* Copyright (C) 2005-2008, by: Angelo Marletta +* +* 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. +* +********************************************************************** +* +* Dynamic list implementation +* +*/ + +#include +#include + +#include "list.h" + +#define EMPTYLIST NULL + +void init_list(struct list *l,int keysize) { + l->first=l->last=NULL; + l->keysize=keysize; + l->count=0; +} + +struct list_node *add_elem(struct list *l,void *elem) { + struct list_node *newnode=(struct list_node*)malloc(sizeof(struct list_node)); + newnode->data=elem; + newnode->previous=l->last; + newnode->next=NULL; + if (l->count==0) { + l->first=l->last=newnode; + } + else { + l->last->next=newnode; + l->last=newnode; + } + l->count++; + return newnode; +} + +void delete_node(struct list *l,struct list_node *node) { + if (l->count==1) { + l->first=l->last=NULL; + } + else if (node==l->first) { + node->next->previous=NULL; + l->first=node->next; + } + else if (node==l->last) { + node->previous->next=NULL; + l->last=node->previous; + } + else { + node->previous->next=node->next; + node->next->previous=node->previous; + } + l->count--; + free(node); + node = NULL; +} + +void destroy_node(struct list *l,struct list_node *node) { + free(node->data); + node->data=NULL; + delete_node(l,node); +} + +int is_EMPTYLIST_list(struct list *l) { + return (l->count==0?TRUE:FALSE); +} + +int get_list_count(struct list *l) { + return l->count; +} + +void *first_elem(struct list *l) { + return l->first->data; +} + +struct list_node *first_node(struct list *l) { + return l->first; +} + +void *last_elem(struct list *l) { + return l->last->data; +} + +struct list_node *last_node(struct list *l) { + return l->last; +} + +struct list_node *xlocate_node(struct list *l,void *elem,int offset,int length) { + struct list_node *tmp; + tmp=l->first; + while(tmp!=NULL) { + if(!memcmp((char*)tmp->data+offset,elem,length==0?l->keysize:length)) return (tmp); + tmp=tmp->next; + } + return EMPTYLIST; +} + +struct list_node *locate_node(struct list *l,void *elem) { + return(xlocate_node(l,elem,0,0)); +} + +void *xlocate_elem(struct list *l,void *elem,int offset,int length) { + struct list_node *node=xlocate_node(l,elem,offset,length); + return(node==NULL?NULL:node->data); +} + +void *locate_elem(struct list *l,void *elem) { + return(xlocate_elem(l,elem,0,0)); +} + +void flush_list(struct list *l) { + struct list_node *tmp; + while(l->first!=EMPTYLIST) { + tmp=l->first; + l->first=l->first->next; + free(tmp); + tmp=NULL; + } + l->last=EMPTYLIST; + l->count=0; +} + +void destroy_list(struct list *l) { + struct list_node *tmp; + while(l->first!=EMPTYLIST) { + tmp=l->first; + l->first=l->first->next; + free(tmp->data); + tmp->data=NULL; + free(tmp); + tmp=NULL; + } + l->last=EMPTYLIST; + l->count=0; +} diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..387f79c --- /dev/null +++ b/src/list.h @@ -0,0 +1,143 @@ +/** + * + * cpulimit - a cpu limiter for Linux + * + * Copyright (C) 2005-2008, by: Angelo Marletta + * + * 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. + * + ********************************************************************** + * + * Dynamic list interface + * + */ + +#ifndef __LIST__ + +#define __LIST__ + +#ifndef TRUE + #define TRUE 1 + #define FALSE 0 +#endif + +struct list_node { + //pointer to the content of the node + void *data; + //pointer to previous node + struct list_node *previous; + //pointer to next node + struct list_node *next; +}; + +struct list { + //first node + struct list_node *first; + //last node + struct list_node *last; + //size of the search key in bytes + int keysize; + //element count + int count; +}; + +/* + * Initialize a list, with a specified key size + */ +void init_list(struct list *l,int keysize); + +/* + * Add a new element at the end of the list + * return the pointer to the new node + */ +struct list_node *add_elem(struct list *l,void *elem); + +/* + * Delete a node + */ +void delete_node(struct list *l,struct list_node *node); + +/* + * Delete a node from the list, even the content pointed by it + * Use only when the content is a dynamically allocated pointer + */ +void destroy_node(struct list *l,struct list_node *node); + +/* + * Check whether a list is empty or not + */ +int is_empty_list(struct list *l); + +/* + * Return the element count of the list + */ +int get_list_count(struct list *l); + +/* + * Return the first element (content of the node) from the list + */ +void *first_elem(struct list *l); + +/* + * Return the first node from the list + */ +struct list_node *first_node(struct list *l); + +/* + * Return the last element (content of the node) from the list + */ +void *last_elem(struct list *l); + +/* + * Return the last node from the list + */ +struct list_node *last_node(struct list *l); + +/* + * Search an element of the list by content + * the comparison is done from the specified offset and for a specified length + * if offset=0, the comparison starts from the address pointed by data + * if length=0, default keysize is used for length + * if the element is found, return the node address + * else return NULL + */ +struct list_node *xlocate_node(struct list *l,void *elem,int offset,int length); + +/* + * The same of xlocate_node(), but return the content of the node + */ +void *xlocate_elem(struct list *l,void *elem,int offset,int length); + +/* + * The same of calling xlocate_node() with offset=0 and length=0 + */ +struct list_node *locate_node(struct list *l,void *elem); + +/* + * The same of locate_node, but return the content of the node + */ +void *locate_elem(struct list *l,void *elem); + +/* + * Delete all the elements in the list + */ +void flush_list(struct list *l); + +/* + * Delete every element in the list, and free the memory pointed by all the node data + */ +void destroy_list(struct list *l); + +#endif diff --git a/src/process.c b/src/process.c new file mode 100644 index 0000000..398d9e9 --- /dev/null +++ b/src/process.c @@ -0,0 +1,233 @@ +/** + * + * cpulimit - a cpu limiter for Linux + * + * Copyright (C) 2005-2008, by: Angelo Marletta + * + * 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. + */ + +//TODO: add documentation to public functions + +#include +#include +#include +#include +#include +#include +#include + +#include "process.h" + +#ifdef __APPLE__ +#include +#include +#endif + +#ifdef __linux__ +int get_proc_info(struct process *p, pid_t pid) { +/* static char statfile[20]; + static char buffer[64]; + int ret; + sprintf(statfile, "/proc/%d/stat", pid); + FILE *fd = fopen(statfile, "r"); + if (fd==NULL) return -1; + fgets(buffer, sizeof(buffer), fd); + fclose(fd); + + char state; + + int n = sscanf(buffer, "%d %s %c %d %d %d %d %d " + "%lu %lu %lu %lu %lu %lu %lu " + "%ld %ld %ld %ld %ld %ld " + "%lu ", + &p->pid, + &p->command, + &state, + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + &utime,&stime,&cutime,&cstime, + NULL,NULL,NULL,NULL, + &starttime, + );*/ + return 0; +} +#elif defined __APPLE__ +int get_proc_info(struct process *p, pid_t pid) { + int err; + struct kinfo_proc *result = NULL; + size_t length; + int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; + + /* We start by calling sysctl with result == NULL and length == 0. + That will succeed, and set length to the appropriate length. + We then allocate a buffer of that size and call sysctl again + with that buffer. + */ + length = 0; + err = sysctl(mib, 4, NULL, &length, NULL, 0); + if (err == -1) { + err = errno; + } + if (err == 0) { + result = malloc(length); + err = sysctl(mib, 4, result, &length, NULL, 0); + if (err == -1) + err = errno; + if (err == ENOMEM) { + free(result); /* clean up */ + result = NULL; + } + } + + p->pid = result->kp_proc.p_pid; + p->ppid = result->kp_eproc.e_ppid; + p->starttime = result->kp_proc.p_starttime.tv_sec; + p->last_jiffies = result->kp_proc.p_cpticks; + //p_pctcpu + + return 0; +} +#endif + +// returns the start time of a process (used with pid to identify a process) +static int get_starttime(pid_t pid) +{ +#ifdef __linux__ + char file[20]; + char buffer[1024]; + sprintf(file, "/proc/%d/stat", pid); + FILE *fd = fopen(file, "r"); + if (fd==NULL) return -1; + if (fgets(buffer, sizeof(buffer), fd) == NULL) return -1; + fclose(fd); + char *p = buffer; + p = memchr(p+1,')', sizeof(buffer) - (p-buffer)); + int sp = 20; + while (sp--) + p = memchr(p+1,' ',sizeof(buffer) - (p-buffer)); + //start time of the process + int time = atoi(p+1); + return time; +#elif defined __APPLE__ + struct process proc; + get_proc_info(&proc, pid); + return proc.starttime; +#endif +} + +static int get_jiffies(struct process *proc) { +#ifdef __linux__ + FILE *f = fopen(proc->stat_file, "r"); + if (f==NULL) return -1; + if (fgets(proc->buffer, sizeof(proc->buffer),f) == NULL) return -1; + fclose(f); + char *p = proc->buffer; + p = memchr(p+1,')', sizeof(proc->buffer) - (p-proc->buffer)); + int sp = 12; + while (sp--) + p = memchr(p+1,' ',sizeof(proc->buffer) - (p-proc->buffer)); + //user mode jiffies + int utime = atoi(p+1); + p = memchr(p+1,' ',sizeof(proc->buffer) - (p-proc->buffer)); + //kernel mode jiffies + int ktime = atoi(p+1); + return utime+ktime; +#elif defined __APPLE__ + struct process proc2; + get_proc_info(&proc2, proc->pid); + return proc2.last_jiffies; +#endif +} + +//return t1-t2 in microseconds (no overflow checks, so better watch out!) +static inline unsigned long timediff(const struct timeval *t1,const struct timeval *t2) +{ + return (t1->tv_sec - t2->tv_sec) * 1000000 + (t1->tv_usec - t2->tv_usec); +} + +/*static int*/ int process_update(struct process *proc) { + //TODO: get any process statistic here + //check that starttime is not changed(?), update jiffies, parent, zombie status + return 0; +} + +int process_init(struct process *proc, int pid) +{ + //general members + proc->pid = pid; + proc->starttime = get_starttime(pid); + proc->cpu_usage = 0; + memset(&(proc->last_sample), 0, sizeof(struct timeval)); + proc->last_jiffies = -1; + //system dependent members +#ifdef __linux__ +//TODO: delete these members for the sake of portability? + //test /proc file descriptor for reading + sprintf(proc->stat_file, "/proc/%d/stat", pid); + FILE *fd = fopen(proc->stat_file, "r"); + if (fd == NULL) return 1; + fclose(fd); +#endif + return 0; +} + +//parameter in range 0-1 +#define ALFA 0.08 + +int process_monitor(struct process *proc) +{ + int j = get_jiffies(proc); + if (j<0) return -1; //error retrieving jiffies count (maybe the process is dead) + struct timeval now; + gettimeofday(&now, NULL); + if (proc->last_jiffies==-1) { + //store current time + proc->last_sample = now; + //store current jiffies + proc->last_jiffies = j; + //it's the first sample, cannot figure out the cpu usage + proc->cpu_usage = -1; + return 0; + } + //time from previous sample (in ns) + long dt = timediff(&now, &(proc->last_sample)); + //how many jiffies in dt? + double max_jiffies = dt * HZ / 1000000.0; + double sample = (j - proc->last_jiffies) / max_jiffies; + if (proc->cpu_usage == -1) { + //initialization + proc->cpu_usage = sample; + } + else { + //usage adjustment + proc->cpu_usage = (1-ALFA) * proc->cpu_usage + ALFA * sample; + } + + //store current time + proc->last_sample = now; + //store current jiffies + proc->last_jiffies = j; + + return 0; +} + +int process_close(struct process *proc) +{ + if (kill(proc->pid,SIGCONT)!=0) { + fprintf(stderr,"Process %d is already dead!\n", proc->pid); + } + proc->pid = 0; + return 0; +} diff --git a/src/process.h b/src/process.h new file mode 100644 index 0000000..0280ad6 --- /dev/null +++ b/src/process.h @@ -0,0 +1,88 @@ +/** + * + * cpulimit - a cpu limiter for Linux + * + * Copyright (C) 2005-2008, by: Angelo Marletta + * + * 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. + */ + +#ifndef __PROCESS_H + +#define __PROCESS_H + +#include +#include +#include + +//USER_HZ detection, from openssl code +#ifndef HZ +# if defined(_SC_CLK_TCK) \ + && (!defined(OPENSSL_SYS_VMS) || __CTRL_VER >= 70000000) +# define HZ ((double)sysconf(_SC_CLK_TCK)) +# else +# ifndef CLK_TCK +# ifndef _BSD_CLK_TCK_ /* FreeBSD hack */ +# define HZ 100.0 +# else /* _BSD_CLK_TCK_ */ +# define HZ ((double)_BSD_CLK_TCK_) +# endif +# else /* CLK_TCK */ +# define HZ ((double)CLK_TCK) +# endif +# endif +#endif + +// process descriptor +struct process { + //pid of the process + pid_t pid; + //pid of the process + pid_t ppid; + //start time + int starttime; + //is member of the family? + int member; //TODO: delete this field + //total number of jiffies used by the process at time last_sample + int last_jiffies; + //timestamp when last_jiffies and cpu_usage was calculated + struct timeval last_sample; + //actual cpu usage estimation (value in range 0-1) + double cpu_usage; + //1 if the process is zombie + int is_zombie; + //absolute path of the executable file + char command[PATH_MAX+1]; + + //system-dependent members +//TODO: delete these members for the sake of portability? +#ifdef __linux__ + //preallocate buffers for performance + //name of /proc/PID/stat file + char stat_file[20]; + //read buffer for /proc filesystem + char buffer[1024]; +#endif +}; + +int get_proc_info(struct process *p, pid_t pid); + +int process_init(struct process *proc, pid_t pid); + +int process_monitor(struct process *proc); + +int process_close(struct process *proc); + +#endif diff --git a/src/procutils.c b/src/procutils.c new file mode 100644 index 0000000..83f3e58 --- /dev/null +++ b/src/procutils.c @@ -0,0 +1,453 @@ +/** + * + * cpulimit - a cpu limiter for Linux + * + * Copyright (C) 2005-2008, by: Angelo Marletta + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "procutils.h" + +#ifdef __APPLE__ +#include +#include +#endif + +/* PROCESS STATISTICS FUNCTIONS */ + +//deprecated +// returns pid of the parent process +static pid_t getppid_of(pid_t pid) +{ +#ifdef __linux__ + char file[20]; + char buffer[1024]; + sprintf(file, "/proc/%d/stat", pid); + FILE *fd = fopen(file, "r"); + if (fd==NULL) return -1; + if (fgets(buffer, sizeof(buffer), fd)==NULL) return -1; + fclose(fd); + char *p = buffer; + p = memchr(p+1,')', sizeof(buffer) - (p-buffer)); + int sp = 2; + while (sp--) + p = memchr(p+1,' ',sizeof(buffer) - (p-buffer)); + //pid of the parent process + pid_t ppid = atoi(p+1); + return ppid; +#elif defined __APPLE__ + struct process p; + get_proc_info(&p, pid); + return p.ppid; +#endif +} + +#ifdef __linux__ +// detects whether a process is a kernel thread or not +static int is_kernel_thread(pid_t pid) +{ + static char statfile[20]; + static char buffer[64]; + int ret; + sprintf(statfile, "/proc/%d/statm", pid); + FILE *fd = fopen(statfile, "r"); + if (fd==NULL) return -1; + if (fgets(buffer, sizeof(buffer), fd)==NULL) return -1; + ret = strncmp(buffer,"0 0 0",3)==0; + fclose(fd); + return ret; +} +#endif + +//deprecated +// returns 1 if pid is an existing pid, 0 otherwise +static int process_exists(pid_t pid) { +#ifdef __linux__ + static char procdir[20]; + struct stat procstat; + sprintf(procdir, "/proc/%d", pid); + return stat(procdir, &procstat)==0; +#elif defined __APPLE__ + struct process p; + return get_proc_info(&p, pid)==0; +#endif +} + +/* PID HASH FUNCTIONS */ + +static int hash_process(struct process_family *f, struct process *p) +{ + int ret; + struct list **l = &(f->proctable[pid_hashfn(p->pid)]); + if (*l == NULL) { + //there is no process in this hashtable item + //allocate the list + *l = (struct list*)malloc(sizeof(struct list)); + init_list(*l, 4); + add_elem(*l, p); + ret = 0; + f->count++; + } + else { + //list already exists + struct process *tmp = (struct process*)locate_elem(*l, p); + if (tmp != NULL) { + //update process info + memcpy(tmp, p, sizeof(struct process)); + free(p); + p = NULL; + ret = 1; + } + else { + //add new process + add_elem(*l, p); + ret = 0; + f->count++; + } + } + return ret; +} + +static void unhash_process(struct process_family *f, pid_t pid) { + //remove process from hashtable + struct list **l = &(f->proctable[pid_hashfn(pid)]); + if (*l == NULL) + return; //nothing done + struct list_node *node = locate_node(*l, &pid); + if (node != NULL) + destroy_node(*l, node); + f->count--; +} + +static struct process *seek_process(struct process_family *f, pid_t pid) +{ + struct list **l = &(f->proctable[pid_hashfn(pid)]); + return (*l != NULL) ? (struct process*)locate_elem(*l, &pid) : NULL; +} + +/* PROCESS ITERATOR STUFF */ + +// creates an object that browse all running processes +int init_process_iterator(struct process_iterator *i) { +#ifdef __linux__ + //open a directory stream to /proc directory + if ((i->dip = opendir("/proc")) == NULL) { + perror("opendir"); + return -1; + } +#elif defined __APPLE__ + + int err; + struct kinfo_proc *result = NULL; + size_t length; + int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; + + /* We start by calling sysctl with result == NULL and length == 0. + That will succeed, and set length to the appropriate length. + We then allocate a buffer of that size and call sysctl again + with that buffer. + */ + length = 0; + err = sysctl(mib, 4, NULL, &length, NULL, 0); + if (err == -1) { + err = errno; + } + if (err == 0) { + result = malloc(length); + err = sysctl(mib, 4, result, &length, NULL, 0); + if (err == -1) + err = errno; + if (err == ENOMEM) { + free(result); /* clean up */ + result = NULL; + } + } + + i->procList = result; + i->count = err == 0 ? length / sizeof *result : 0; + i->c = 0; + +#endif + i->current = (struct process*)malloc(sizeof(struct process)); + memset(i->current, 0, sizeof(struct process)); + return 0; +} + +// reads the next user process from /process +// automatic closing if the end of the list is reached +int read_next_process(struct process_iterator *i) { +#ifdef __linux__ + pid_t pid = 0; +//TODO read this to port to other systems: http://www.steve.org.uk/Reference/Unix/faq_8.html#SEC85 + //read in from /proc and seek for process dirs + while ((i->dit = readdir(i->dip)) != NULL) { + if(strtok(i->dit->d_name, "0123456789") != NULL) + continue; + pid = atoi(i->dit->d_name); + if (is_kernel_thread(pid)) + continue; + //return the first found process + break; + } + if (pid == 0) { + //no more processes + closedir(i->dip); + free(i->current); + i->current = NULL; + return -1; + } + //read the executable link + char statfile[20]; + sprintf(statfile,"/proc/%d/cmdline",pid); + FILE *fd = fopen(statfile, "r"); + if (fd == NULL) return -1; + char buffer[1024]; + if (fgets(buffer, sizeof(buffer), fd)==NULL) return -2; + fclose(fd); + sscanf(buffer, "%s", (char*)&i->current->command); + i->current->pid = pid; + +#elif defined __APPLE__ + if (i->c >= i->count) { + //no more processes + free(i->procList); + i->procList = NULL; + free(i->current); + i->current = NULL; + return -1; + } + i->current->pid = i->procList[i->c].kp_proc.p_pid; + strncpy(i->current->command, i->procList[i->c].kp_proc.p_comm, MAXCOMLEN); +printf("%d %d %s\n", i->c, i->current->pid, i->current->command);//i->procList[i->c].kp_proc.p_comm); +//printf("%d %d %s\n", i->c, i->current->pid, i->proc[i->c].kp_proc.p_comm); + i->c++; +#endif + return 0; +} + +/* PUBLIC FUNCTIONS */ + +// search for all the processes derived from father and stores them +// in the process family struct +int create_process_family(struct process_family *f, pid_t father) +{ + //process list initialization (4 bytes key) + init_list(&(f->members), 4); + //hashtable initialization + memset(&(f->proctable), 0, sizeof(f->proctable)); + f->count = 0; + f->father = father; + //process iterator + struct process_iterator iter; + init_process_iterator(&iter); + int pid = 0; + while (read_next_process(&iter)==0) { + pid = iter.current->pid; + //check if process belongs to the family + int ppid = pid; + //TODO: optimize adding also these parents, and continue if process is already present + while(ppid!=1 && ppid!=father) { + ppid = getppid_of(ppid); + } + //allocate process descriptor + struct process *p = (struct process*)malloc(sizeof(struct process)); + //init process + process_init(p, pid); + if (ppid==1) { + //the init process + p->member = 0; + } + else if (pid != getpid()) { + //add to members (but exclude the current cpulimit process!) + p->member = 1; + add_elem(&(f->members), p); + } + //add to hashtable + hash_process(f, p); + } + return 0; +} + +// checks if there are new processes born in the specified family +// if any they are added to the members list +// the number of new born processes is returned +int update_process_family(struct process_family *f) +{ + int ret = 0; + //process iterator + struct process_iterator iter; + init_process_iterator(&iter); + int pid = 0; + while (read_next_process(&iter)==0) { + pid = iter.current->pid; + struct process *newp = seek_process(f, pid); + if (newp != NULL) continue; //already known //TODO: what if newp is a new process with the same PID?? + //the process is new, check if it belongs to the family + int ppid = getppid_of(pid); + //search the youngest known ancestor of the process + struct process *ancestor = NULL; + while((ancestor=seek_process(f, ppid))==NULL) { + ppid = getppid_of(ppid); + } + if (ancestor == NULL) { + //this should never happen! if does, find and correct the bug + fprintf(stderr, "Fatal bug! Process %d is without parent\n", pid); + exit(1); + } + //allocate and insert the process + struct process *p = (struct process*)malloc(sizeof(struct process)); + //init process + process_init(p, pid); + if (ancestor->member) { + //add to members + p->member = 1; + add_elem(&(f->members), p); + ret++; + } + else { + //not a member + p->member = 0; + } + //add to hashtable + hash_process(f, p); + } + return ret; +} + +// removes a process from the family by its pid +void remove_process_from_family(struct process_family *f, pid_t pid) +{ + struct list_node *node = locate_node(&(f->members), &pid); + if (node != NULL) { +// struct process *p = (struct process*)(node->data); +// free(p->history); +// p->history = NULL; + delete_node(&(f->members), node); + } + unhash_process(f, pid); +} + +// free the heap memory used by a process family +void cleanup_process_family(struct process_family *f) +{ + int i; + int size = sizeof(f->proctable) / sizeof(struct process*); + for (i=0; iproctable[i] != NULL) { + //free() history for each process + struct list_node *node = NULL; + for (node=f->proctable[i]->first; node!=NULL; node=node->next) { +// struct process *p = (struct process*)(node->data); +// free(p->history); +// p->history = NULL; + } + destroy_list(f->proctable[i]); + free(f->proctable[i]); + f->proctable[i] = NULL; + } + } + flush_list(&(f->members)); + f->count = 0; + f->father = 0; +} + +// look for a process by pid +// search_pid : pid of the wanted process +// return: pid of the found process, if it is found +// 0, if it's not found +// negative pid, if it is found but it's not possible to control it +int look_for_process_by_pid(pid_t pid) +{ + if (process_exists(pid)) + return (kill(pid,0)==0) ? pid : -pid; + return 0; +} + +// look for a process with a given name +// process: the name of the wanted process. it can be an absolute path name to the executable file +// or just the file name +// return: pid of the found process, if it is found +// 0, if it's not found +// negative pid, if it is found but it's not possible to control it +int look_for_process_by_name(const char *process_name) +{ + //whether the variable process_name is the absolute path or not + int is_absolute_path = process_name[0] == '/'; + //flag indicating if the a process with given name was found + int found = 0; + + //process iterator + struct process_iterator iter; + init_process_iterator(&iter); + pid_t pid = 0; + +printf("name\n"); + + while (read_next_process(&iter)==0) { + pid = iter.current->pid; + + int size = strlen(iter.current->command); + + found = 0; + if (is_absolute_path && strncmp(iter.current->command, process_name, size)==0 && size==strlen(process_name)) { + //process found + found = 1; + } + else { + //process found + if (strncmp(iter.current->command + size - strlen(process_name), process_name, strlen(process_name))==0) { + found = 1; + } + } + if (found==1) { + if (kill(pid,SIGCONT)==0) { + //process is ok! + break; + } + else { + //we don't have permission to send signal to that process + //so, don't exit from the loop and look for another one with the same name + found = -1; + } + } + } + if (found == 1) { + //ok, the process was found + return pid; + } + else if (found == 0) { + //no process found + return 0; + } + else if (found == -1) { + //the process was found, but we haven't permission to control it + return -pid; + } + //this MUST NOT happen + return 0; +} + diff --git a/src/procutils.h b/src/procutils.h new file mode 100644 index 0000000..b5a91a3 --- /dev/null +++ b/src/procutils.h @@ -0,0 +1,94 @@ +/** + * + * cpulimit - a cpu limiter for Linux + * + * Copyright (C) 2005-2008, by: Angelo Marletta + * + * 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. + */ + +#ifndef __PROCUTILS_H + +#define __PROCUTILS_H + +#include +#include + +#include "list.h" +#include "process.h" + +#define PIDHASH_SZ 1024 +#define pid_hashfn(x) ((((x) >> 8) ^ (x)) & (PIDHASH_SZ - 1)) + +// a hierarchy of processes +struct process_family { + //the (god)father of the process family + pid_t father; + //process list (father process is the first element) + //elements are struct process + struct list members; + //non-members list + //hashtable with all the processes (array of struct list of struct process) + struct list *proctable[PIDHASH_SZ]; + //total process count + int count; +}; + +//TODO: use this object in proctable and delete member in struct process +struct table_item { + struct process *proc; + //1 if the process is a member of the family + int member; +}; + +// object to enumerate running processes +struct process_iterator { +#ifdef __linux__ + DIR *dip; + struct dirent *dit; +#elif defined __APPLE__ + struct kinfo_proc *procList; + int count; + int c; +#elif defined __hpux + int count; +#endif + struct process *current; +}; + +// searches for all the processes derived from father and stores them +// in the process family struct +int create_process_family(struct process_family *f, pid_t father); + +// checks if there are new processes born in the specified family +// if any they are added to the members list +// the number of new born processes is returned +int update_process_family(struct process_family *f); + +// removes a process from the family by its pid +void remove_process_from_family(struct process_family *f, pid_t pid); + +// free the heap memory used by a process family +void cleanup_process_family(struct process_family *f); + +// searches a process given the name of the executable file, or its absolute path +// returns the pid, or 0 if it's not found +int look_for_process_by_name(const char *process_name); + +// searches a process given its pid +// returns the pid, or 0 if it's not found +int look_for_process_by_pid(pid_t pid); + +#endif -- cgit v1.2.3