[svn-upgrade] Integrating new upstream version, cpulimit (1.12~svn10) r1216/tags/upstream/1.12_svn10 upstream/1.2_svn10
authorgregor herrmann <gregoa@debian.org>
Thu, 13 Mar 2008 22:42:31 +0000 (22:42 -0000)
committergregor herrmann <gregoa@debian.org>
Thu, 13 Mar 2008 22:42:31 +0000 (22:42 -0000)
Makefile
README [new file with mode: 0644]
cpulimit.c
list.c [new file with mode: 0644]
list.h [new file with mode: 0644]
process.c [new file with mode: 0644]
process.h [new file with mode: 0644]
procutils.c [new file with mode: 0644]
procutils.h [new file with mode: 0644]
ptest.c [new file with mode: 0644]

index 9013c27..261a76a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,25 @@
-all::  cpulimit
+CC=gcc
+CFLAGS=-Wall -D_GNU_SOURCE -O2
+TARGETS=cpulimit ptest
+LIBS=process.o procutils.o list.o
 
-cpulimit:      cpulimit.c
-       gcc -o cpulimit cpulimit.c -lrt -Wall -O2
+all::  $(TARGETS)
+
+cpulimit:      cpulimit.c $(LIBS)
+       $(CC) -o cpulimit cpulimit.c $(LIBS) -lrt $(CFLAGS)
+
+ptest: ptest.c process.o procutils.o list.o
+       $(CC) -o ptest ptest.c process.o procutils.o list.o -lrt $(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 *~ cpulimit
+       rm -f *~ *.o $(TARGETS)
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..6ffa82c
--- /dev/null
+++ b/README
@@ -0,0 +1,47 @@
+Cpulimit 1.2
+
+=======
+About:
+=======
+
+Cpulimit is a program that attempts to limit the cpu usage of a process (expressed in percentage, not in cpu time). This is useful to control batch jobs, when you don't want they eat too much cpu. It does not act on the nice value or other scheduling priority stuff, but on the real cpu usage. Also, it is able to adapt itself to the overall system load, dynamically and quickly.
+The control of the used cpu amount is done sending continue and stop POSIX signals to processes.
+All the children processes and threads of the specified process will share the same percent of cpu.
+
+Developed by Angelo Marletta.
+Please send your feedback, bug reports, feature requests to marlonx80 at hotmail dot com.
+
+===========
+Changelog:
+===========
+
+- reorganization of the code, splitted in more source files
+- control function process_monitor() optimized by eliminating an unnecessary loop
+- experimental support for multiple control of children processes and threads
+  children detection algorithm seems heavy because of the amount of code,
+  but it's designed to be scalable when there are a lot of children processes
+- cpu count detection, i.e. if you have 4 cpu, it is possible to limit up to 400%
+- in order to avoid deadlock, cpulimit prevents to limit itself
+- option --path eliminated, use --exe instead both for absolute path and file name
+- deleted every setpriority(), (todo: set it just once)
+- minor enhancements and bugfixes
+
+
+============================
+Get the latest source code:
+============================
+
+You can checkout the latest code from sourceforge Subversion repository, running:
+
+svn checkout https://cpulimit.svn.sourceforge.net/svnroot/cpulimit cpulimit
+
+Of course this is the development version, so you may experience bugs (please signal them!)
+
+
+==============
+Installation:
+==============
+
+Run 'make' and place the executable file 'cpulimit' wherever you want.
+Type 'cpulimit --help' to get documentation on available options.
+
index bd2ea0a..bf912d6 100644 (file)
@@ -1,36 +1,50 @@
 /**
- * Copyright (c) 2005, by:      Angelo Marletta <marlonx80@hotmail.com>
  *
- * This file may be used subject to the terms and conditions of the
- * GNU Library General Public License Version 2, or any later version
- * at your option, as published by the Free Software Foundation.
+ * cpulimit - a cpu limiter for Linux
+ *
+ * Copyright (C) 2005-2008, by:  Angelo Marletta <marlonx80@hotmail.com>
+ *
+ * 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 Library General Public License for more details.
+ * 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.
  *
  **********************************************************************
  *
- * Simple program to limit the cpu usage of a process
+ * This is a 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
- * Last version at: http://marlon80.interfree.it/cpulimit/index.html
+ * Date:    15/2/2008
+ * Version: 1.2 alpha
+ * Get the latest version at: http://cpulimit.sourceforge.net
  *
  * Changelog:
- *  - 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 <limits.h>
- *  - Command line arguments now available
- *  - Now is possible to specify target process by pid
+ * - reorganization of the code, splitted in more source files
+ * - control function process_monitor() optimized by eliminating an unnecessary loop
+ * - experimental support for multiple control of children processes and threads
+ *   children detection algorithm seems heavy because of the amount of code,
+ *   but it's designed to be scalable when there are a lot of children processes
+ * - cpu count detection, i.e. if you have 4 cpu, it is possible to limit up to 400%
+ * - in order to avoid deadlock, cpulimit prevents to limit itself
+ * - option --path eliminated, use --exe instead both for absolute path and file name
+ * - deleted almost every setpriority(), just set it once at startup
+ * - minor enhancements and bugfixes
+ *
  */
 
 
 #include <getopt.h>
 #include <stdio.h>
+#include <fcntl.h>
 #include <stdlib.h>
 #include <time.h>
 #include <sys/time.h>
 #include <errno.h>
 #include <string.h>
 
-//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
+#include "process.h"
+#include "procutils.h"
+#include "list.h"
 
 //some useful macro
-#define min(a,b) (a<b?a:b)
-#define max(a,b) (a>b?a:b)
-
-//pid of the controlled process
-int pid=0;
-//executable file name
+#define MIN(a,b) (a<b?a:b)
+#define MAX(a,b) (a>b?a:b)
+#define print_caption()        printf("\n%%CPU\twork quantum\tsleep quantum\tactive rate\n")
+//control time slot in microseconds
+//each slot is splitted in a working slice and a sleeping slice
+#define CONTROL_SLOT 100000
+
+#define MAX_PRIORITY -10
+
+//the "family"
+struct process_family pf;
+//pid of cpulimit
+int cpulimit_pid;
+//name of this program (maybe cpulimit...)
 char *program_name;
+
+/* CONFIGURATION VARIABLES */
+
 //verbose mode
-int verbose=0;
-//lazy mode
-int lazy=0;
+int verbose = 0;
+//lazy mode (exits if there is no process)
+int lazy = 0;
+
+//how many cpu do we have?
+int get_cpu_count()
+{
+       FILE *fd;
+       int cpu_count = 0;
+       char line[100];
+       fd = fopen("/proc/stat", "r");
+       if (fd < 0)
+               return 0; //are we running Linux??
+       while (fgets(line,sizeof(line),fd)!=NULL) {
+               if (strncmp(line, "cpu", 3) != 0) break;
+               cpu_count++;
+       }
+       fclose(fd);
+       return cpu_count - 1;
+}
 
-//reverse byte search
-void *memrchr(const void *s, int c, size_t n);
+//return t1-t2 in microseconds (no overflow checks, so better watch out!)
+inline unsigned long timediff(const struct timespec *t1,const struct timespec *t2)
+{
+       return (t1->tv_sec - t2->tv_sec) * 1000000 + (t1->tv_nsec/1000 - t2->tv_nsec/1000);
+}
 
-//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;
+//returns t1-t2 in microseconds
+inline unsigned long long tv_diff(struct timeval *t1, struct timeval *t2)
+{
+       return ((unsigned long long)(t1->tv_sec - t2->tv_sec)) * 1000000ULL + t1->tv_usec - t2->tv_usec;
 }
 
-int waitforpid(int pid) {
-       //switch to low priority
-       if (setpriority(PRIO_PROCESS,getpid(),19)!=0) {
-               printf("Warning: cannot renice\n");
+//SIGINT and SIGTERM signal handler
+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);
+               process_close(p->history);
+               kill(p->pid, SIGCONT);
        }
+       //free all the memory
+       cleanup_process_family(&pf);
+       exit(0);
+}
 
-       int i=0;
-
-       while(1) {
+void print_usage(FILE *stream, int exit_code)
+{
+       fprintf(stream, "Usage: %s TARGET [OPTIONS...]\n",program_name);
+       fprintf(stream, "   TARGET must be exactly one of these:\n");
+       fprintf(stream, "      -p, --pid=N        pid of the process (implies -z)\n");
+       fprintf(stream, "      -e, --exe=FILE     name of the executable program file or absolute path name\n");
+       fprintf(stream, "   OPTIONS\n");
+       fprintf(stream, "      -l, --limit=N      percentage of cpu allowed from 0 to 100 (required)\n");
+       fprintf(stream, "      -v, --verbose      show control statistics\n");
+       fprintf(stream, "      -z, --lazy         exit if there is no suitable target process, or if it dies\n");
+       fprintf(stream, "      -h, --help         display this help and exit\n");
+       exit(exit_code);
+}
 
-               DIR *dip;
-               struct dirent *dit;
+void limit_process(int pid, float limit)
+{
+       //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 timespec startwork;
+       //when the last twork has finished
+       struct timespec endwork;
+       //initialization
+       memset(&twork, 0, sizeof(struct timespec));
+       memset(&tsleep, 0, sizeof(struct timespec));
+       memset(&startwork, 0, sizeof(struct timespec));
+       memset(&endwork, 0, sizeof(struct timespec));   
+       //last working time in microseconds
+       unsigned long workingtime = 0;
+       int i = 0;
+
+       //build the family
+       create_process_family(&pf, pid);
+       struct list_node *node;
+       
+       if (verbose) printf("Members in the family owned by %d: %d\n", pf.father, pf.members.count);
 
-               //open a directory stream to /proc directory
-               if ((dip = opendir("/proc")) == NULL) {
-                       perror("opendir");
-                       return -1;
-               }
+       while(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
-                               if (kill(pid,SIGSTOP)==0 &&  kill(pid,SIGCONT)==0) {
-                                       //process is ok!
-                                       goto done;
-                               }
-                               else {
-                                       fprintf(stderr,"Error: Process %d detected, but you don't have permission to control it\n",pid);
+               if (i%100==0 && verbose) print_caption();
+
+               if (i%10==0) {
+                       //update the process family (checks only for new members)
+                       int newborn = check_new_members(&pf);
+                       if (newborn) {
+                               printf("%d new children processes detected (", newborn);
+                               int j;
+                               node = pf.members.last;
+                               for (j=0; j<newborn; j++) {
+                                       printf("%d", ((struct process*)(node->data))->pid);
+                                       if (j<newborn-1) printf(" ");
+                                       node = node->previous;
                                }
+                               printf(")\n");
                        }
                }
 
-               //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");
+               //total cpu actual usage (range 0-1)
+               //1 means that the processes are using 100% cpu
+               float pcpu = 0;
+               //rate at which we are keeping active the processes (range 0-1)
+               //1 means that the process are using all the twork slice
+               float workingrate = 0;
+
+               //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 (process_monitor(proc->history, workingtime, &(proc->history->usage))==-1) {
+                               //process is dead, remove it from family
+                               remove_process_from_family(&pf, proc->pid);
+                               fprintf(stderr,"Process %d dead!\n", proc->pid);
                        }
+                       pcpu += proc->history->usage.pcpu;
+                       workingrate += proc->history->usage.workingrate;
                }
+               //average value
+               workingrate /= pf.members.count;
 
-               //sleep for a while
-               sleep(2);
-       }
+               //TODO: make workingtime customized for each process, now it's equal for all
 
-done:
-       printf("Process %d detected\n",pid);
-       //now set high priority, if possible
-       if (setpriority(PRIO_PROCESS,getpid(),-20)!=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) {
-               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;
+               //adjust work and sleep time slices
+               if (pcpu>0) {
+                       twork.tv_nsec = MIN(CONTROL_SLOT*limit*1000/pcpu*workingrate,CONTROL_SLOT*1000);
                }
-
-               //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) {
-                                               if (kill(pid,SIGSTOP)==0 &&  kill(pid,SIGCONT)==0) {
-                                                       //process is ok!
-                                                       goto done;
-                                               }
-                                               else {
-                                                       fprintf(stderr,"Error: Process %d detected, but you don't have permission to control it\n",pid);
-                                               }
-                                       }
-                               }
-                       }
+               else if (pcpu==0) {
+                       twork.tv_nsec = CONTROL_SLOT*1000;
                }
+               else if (pcpu==-1) {
+                       //not yet a valid idea of cpu usage
+                       pcpu = limit;
+                       workingrate = limit;
+                       twork.tv_nsec = MIN(CONTROL_SLOT*limit*1000,CONTROL_SLOT*1000);
+               }
+               tsleep.tv_nsec = CONTROL_SLOT*1000-twork.tv_nsec;
 
-               //close the dir stream and check for errors
-               if (closedir(dip) == -1) {
-                       perror("closedir");
-                       return -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);
                }
 
-               //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");
+               //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
+                               fprintf(stderr,"Process %d dead!\n", proc->pid);
+                               remove_process_from_family(&pf, proc->pid);
                        }
                }
 
-               //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) {
-               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);
-}
-
-//get jiffies count from /proc filesystem
-int getjiffies(int pid) {
-       static char stat[20];
-       static char buffer[1024];
-       sprintf(stat,"/proc/%d/stat",pid);
-       FILE *f=fopen(stat,"r");
-       if (f==NULL) return -1;
-       fgets(buffer,sizeof(buffer),f);
-       fclose(f);
-       char *p=buffer;
-       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;
-}
-
-//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
-       clock_gettime(CLOCK_REALTIME,&(ps[front].when));
-       ps[front].cputime=last_working_quantum;
-
-       //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;
+               //now processes are free to run (same working slice for all)
+               clock_gettime(CLOCK_REALTIME,&startwork);
+               nanosleep(&twork,NULL);
+               clock_gettime(CLOCK_REALTIME,&endwork);
+               workingtime = timediff(&endwork,&startwork);
+
+               //stop processes, they have worked enough
+               //resume processes
+               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
+                               fprintf(stderr,"Process %d dead!\n", proc->pid);
+                               remove_process_from_family(&pf, proc->pid);
+                       }
+               }
+               //now the process is forced to sleep
+               nanosleep(&tsleep,NULL);
+               i++;
        }
-       #undef MEM_ORDER
-}
-
-void print_caption() {
-       printf("\n%%CPU\twork quantum\tsleep quantum\tactive rate\n");
-}
-
-void print_usage(FILE *stream,int exit_code) {
-       fprintf(stream, "Usage: %s TARGET [OPTIONS...]\n",program_name);
-       fprintf(stream, "   TARGET must be exactly one of these:\n");
-       fprintf(stream, "      -p, --pid=N        pid of the process\n");
-       fprintf(stream, "      -e, --exe=FILE     name of the executable program file\n");
-       fprintf(stream, "      -P, --path=PATH    absolute path name of the executable program file\n");
-       fprintf(stream, "   OPTIONS\n");
-       fprintf(stream, "      -l, --limit=N      percentage of cpu allowed from 0 to 100 (mandatory)\n");
-       fprintf(stream, "      -v, --verbose      show control statistics\n");
-       fprintf(stream, "      -z, --lazy         exit if there is no suitable target process, or if it dies\n");
-       fprintf(stream, "      -h, --help         display this help and exit\n");
-       exit(exit_code);
+       cleanup_process_family(&pf);
 }
 
 int main(int argc, char **argv) {
@@ -351,59 +268,60 @@ 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);
+       cpulimit_pid = getpid();
+
+       //argument variables
+       const char *exe = NULL;
+       int perclimit = 0;
+       int pid_ok = 0;
+       int process_ok = 0;
+       int limit_ok = 0;
+       int pid = 0;
+
        //parse arguments
        int next_option;
-       /* A string listing valid short options letters. */
-       const char* short_options="p:e:P:l:vzh";
-       /* An array describing valid long options. */
+    int option_index = 0;
+       //A string listing valid short options letters
+       const char* short_options = "p:e:l:vzh";
+       //An array describing valid long options
        const struct option long_options[] = {
-               { "pid", 0, NULL, 'p' },
-               { "exe", 1, NULL, 'e' },
-               { "path", 0, NULL, 'P' },
-               { "limit", 0, NULL, 'l' },
-               { "verbose", 0, NULL, 'v' },
-               { "lazy", 0, NULL, 'z' },
-               { "help", 0, NULL, 'h' },
-               { NULL, 0, NULL, 0 }
+               { "pid",        required_argument, NULL,     'p' },
+               { "exe",        required_argument, NULL,     'e' },
+               { "limit",      required_argument, NULL,     'l' },
+               { "verbose",    no_argument,       &verbose, 'v' },
+               { "lazy",       no_argument,       &lazy,    'z' },
+               { "help",       no_argument,       NULL,     'h' },
+               { 0,            0,                 0,         0  }
        };
-       //argument variables
-       const char *exe=NULL;
-       const char *path=NULL;
-       int perclimit=0;
-       int pid_ok=0;
-       int process_ok=0;
-       int limit_ok=0;
 
        do {
-               next_option = getopt_long (argc, argv, short_options,long_options, NULL);
+               next_option = getopt_long(argc, argv, short_options,long_options, &option_index);
                switch(next_option) {
                        case 'p':
-                               pid=atoi(optarg);
-                               pid_ok=1;
+                               pid = atoi(optarg);
+                               //todo: verify pid is valid
+                               pid_ok = 1;
+                               process_ok = 1;
                                break;
                        case 'e':
-                               exe=optarg;
-                               process_ok=1;
-                               break;
-                       case 'P':
-                               path=optarg;
-                               process_ok=1;
+                               exe = optarg;
+                               process_ok = 1;
                                break;
                        case 'l':
-                               perclimit=atoi(optarg);
-                               limit_ok=1;
+                               perclimit = atoi(optarg);
+                               limit_ok = 1;
                                break;
                        case 'v':
-                               verbose=1;
+                               verbose = 1;
                                break;
                        case 'z':
-                               lazy=1;
+                               lazy = 1;
                                break;
                        case 'h':
-                               print_usage (stdout, 1);
+                               print_usage(stdout, 1);
                                break;
                        case '?':
-                               print_usage (stderr, 1);
+                               print_usage(stderr, 1);
                                break;
                        case -1:
                                break;
@@ -412,126 +330,91 @@ int main(int argc, char **argv) {
                }
        } while(next_option != -1);
 
-       if (!process_ok && !pid_ok) {
-               fprintf(stderr,"Error: You must specify a target process\n");
-               print_usage (stderr, 1);
+       if (pid!=0) {
+               lazy = 1;
+       }
+       
+       if (!process_ok) {
+               fprintf(stderr,"Error: You must specify a target process, by name or by PID\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);
+       if (pid_ok && exe!=NULL) {
+               fprintf(stderr, "Error: You must specify exactly one process, by name or by PID\n");
+               print_usage(stderr, 1);
                exit(1);
        }
        if (!limit_ok) {
-               fprintf(stderr,"Error: You must specify a cpu limit\n");
-               print_usage (stderr, 1);
+               fprintf(stderr,"Error: You must specify a cpu limit percentage\n");
+               print_usage(stderr, 1);
                exit(1);
        }
-       float limit=perclimit/100.0;
-       if (limit<0 || limit >1) {
-               fprintf(stderr,"Error: limit must be in the range 0-100\n");
-               print_usage (stderr, 1);
+       float limit = perclimit/100.0;
+       int cpu_count = get_cpu_count();
+       if (limit<0 || limit >cpu_count) {
+               fprintf(stderr,"Error: limit must be in the range 0-%d00\n", cpu_count);
+               print_usage(stderr, 1);
                exit(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)
-               pid=getpidof(exe);
-       else if (path!=NULL)
-               pid=getpidof(path);
+       signal(SIGINT, quit);
+       signal(SIGTERM, quit);
+
+       //try to renice with the best 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) {
+               printf("Priority changed to %d\n", priority);
+       }
        else {
-               waitforpid(pid);
+               printf("Cannot change priority\n");
        }
-       //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);
+               //look for the target process..or wait for it
+               int 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");
+                       }
                }
-
-               if (limit<1 && limit>0) {
-                       //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;
+               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;
                        }
                }
-
-               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) {
-                               fprintf(stderr,"Process %d dead!\n",pid);
-                               if (lazy) exit(2);
-                               //wait until our process appears
-                               goto wait_for_process;
+               if (ret > 0) {
+                       if (ret == cpulimit_pid) {
+                               printf("Process %d is cpulimit itself! Aborting to avoid deadlock\n", ret);
+                               exit(1);
                        }
-                       nanosleep(&tsleep,NULL);        //now process is sleeping
+                       printf("Process %d found\n", pid);
+                       //control
+                       limit_process(pid, limit);
                }
-               i++;
+               if (lazy) {
+                       printf("Giving up...\n");
+                       break;
+               }
+               sleep(2);
        }
-
+       
+       return 0;
 }
+
diff --git a/list.c b/list.c
new file mode 100644 (file)
index 0000000..6ec1fd4
--- /dev/null
+++ b/list.c
@@ -0,0 +1,150 @@
+/**
+ *
+ * cpulimit - a cpu limiter for Linux
+ *
+ * Copyright (C) 2005-2008, by:  Angelo Marletta <marlonx80@hotmail.com>
+ *
+ * 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 "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=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);
+}
+
+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(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/list.h b/list.h
new file mode 100644 (file)
index 0000000..d505665
--- /dev/null
+++ b/list.h
@@ -0,0 +1,147 @@
+/**
+ *
+ * cpulimit - a cpu limiter for Linux
+ *
+ * Copyright (C) 2005-2008, by:  Angelo Marletta <marlonx80@hotmail.com>
+ *
+ * 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__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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/process.c b/process.c
new file mode 100644 (file)
index 0000000..e044fdc
--- /dev/null
+++ b/process.c
@@ -0,0 +1,117 @@
+/**
+ *
+ * cpulimit - a cpu limiter for Linux
+ *
+ * Copyright (C) 2005-2008, by:  Angelo Marletta <marlonx80@hotmail.com>
+ *
+ * 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 "process.h"
+
+int process_init(struct process_history *proc, int pid)
+{
+       proc->pid = pid;
+       //init circular queue
+       proc->front_index = -1;
+       proc->tail_index = 0;
+       proc->actual_history_size = 0;
+       memset(proc->history, 0, sizeof(proc->history));
+       proc->total_workingtime = 0;
+       //test /proc file descriptor for reading
+       sprintf(proc->stat_file, "/proc/%d/stat", proc->pid);
+       FILE *fd = fopen(proc->stat_file, "r");
+       fclose(fd);
+       proc->usage.pcpu = 0;
+       proc->usage.workingrate = 0;
+       return (fd == NULL);
+}
+
+//return t1-t2 in microseconds (no overflow checks, so better watch out!)
+static inline unsigned long timediff(const struct timespec *t1,const struct timespec *t2)
+{
+       return (t1->tv_sec - t2->tv_sec) * 1000000 + (t1->tv_nsec/1000 - t2->tv_nsec/1000);
+}
+
+static int get_jiffies(struct process_history *proc) {
+       FILE *f = fopen(proc->stat_file, "r");
+       if (f==NULL) return -1;
+       fgets(proc->buffer, sizeof(proc->buffer),f);
+       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;
+}
+
+int process_monitor(struct process_history *proc, int last_working_quantum, struct cpu_usage *usage)
+{
+       //increment front index
+       proc->front_index = (proc->front_index+1) % HISTORY_SIZE;
+
+       //actual history size is: (front-tail+HISTORY_SIZE)%HISTORY_SIZE+1
+       proc->actual_history_size = (proc->front_index - proc->tail_index + HISTORY_SIZE) % HISTORY_SIZE + 1;
+
+       //actual front and tail of the queue
+       struct process_screenshot *front = &(proc->history[proc->front_index]);
+       struct process_screenshot *tail = &(proc->history[proc->tail_index]);
+
+       //take a shot and save in front
+       int j = get_jiffies(proc);
+       if (j<0) return -1; //error retrieving jiffies count (maybe the process is dead)
+       front->jiffies = j;
+       clock_gettime(CLOCK_REALTIME, &(front->when));
+       front->cputime = last_working_quantum;
+
+       if (proc->actual_history_size==1) {
+               //not enough elements taken (it's the first one!), return 0
+               usage->pcpu = -1;
+               usage->workingrate = 1;
+               return 0;
+       }
+       else {
+               //queue has enough elements
+               //now we can calculate cpu usage, interval dt and dtwork are expressed in microseconds
+               long dt = timediff(&(front->when), &(tail->when));
+               //the total time between tail and front in which the process was allowed to run
+               proc->total_workingtime += front->cputime - tail->cputime;
+               int used_jiffies = front->jiffies - tail->jiffies;
+               float usage_ratio = (used_jiffies*1000000.0/HZ) / proc->total_workingtime;
+               usage->workingrate = 1.0 * proc->total_workingtime / dt;
+               usage->pcpu = usage_ratio * usage->workingrate;
+               //increment tail index if the queue is full
+               if (proc->actual_history_size==HISTORY_SIZE)
+                       proc->tail_index = (proc->tail_index+1) % HISTORY_SIZE;
+               return 0;
+       }
+}
+
+int process_close(struct process_history *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/process.h b/process.h
new file mode 100644 (file)
index 0000000..16cbf8f
--- /dev/null
+++ b/process.h
@@ -0,0 +1,84 @@
+/**
+ *
+ * cpulimit - a cpu limiter for Linux
+ *
+ * Copyright (C) 2005-2008, by:  Angelo Marletta <marlonx80@hotmail.com>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+//kernel time resolution (inverse of one jiffy interval) in Hertz
+#define HZ sysconf(_SC_CLK_TCK)
+//size of the history circular queue
+#define HISTORY_SIZE 10
+
+//extracted process statistics
+struct cpu_usage {
+       float pcpu;
+       float workingrate;
+};
+
+//process instant photo
+struct process_screenshot {
+       //timestamp
+       struct timespec when;
+       //total jiffies used by the process
+       int jiffies;
+       //cpu time used since previous screenshot expressed in microseconds
+       int cputime;
+};
+
+//process descriptor
+struct process_history {
+       //the PID of the process
+       int pid;
+       //name of /proc/PID/stat file
+       char stat_file[20];
+       //read buffer for /proc filesystem
+       char buffer[1024];
+       //recent history of the process (fixed circular queue)
+       struct process_screenshot history[HISTORY_SIZE];
+       //the index of the last screenshot made to the process
+       int front_index;
+       //the index of the oldest screenshot made to the process
+       int tail_index;
+       //how many screenshots we have in the queue (max HISTORY_SIZE)
+       int actual_history_size;
+       //total cputime saved in the history
+       long total_workingtime;
+       //current usage
+       struct cpu_usage usage;
+};
+
+int process_init(struct process_history *proc, int pid);
+
+int process_monitor(struct process_history *proc, int last_working_quantum, struct cpu_usage *usage);
+
+int process_close(struct process_history *proc);
+
+#endif
diff --git a/procutils.c b/procutils.c
new file mode 100644 (file)
index 0000000..c203daf
--- /dev/null
@@ -0,0 +1,391 @@
+/**
+ *
+ * cpulimit - a cpu limiter for Linux
+ *
+ * Copyright (C) 2005-2008, by:  Angelo Marletta <marlonx80@hotmail.com>
+ *
+ * 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 <sys/utsname.h>
+#include "procutils.h"
+
+/* PROCESS STATISTICS FUNCTIONS */
+
+// returns pid of the parent process
+static int getppid_of(int pid)
+{
+       char file[20];
+       char buffer[1024];
+       sprintf(file, "/proc/%d/stat", pid);
+       FILE *fd = fopen(file, "r");
+               if (fd==NULL) return -1;
+       fgets(buffer, sizeof(buffer), fd);
+       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
+       int ppid = atoi(p+1);
+       return ppid;
+}
+
+// returns the start time of a process (used with pid to identify a process)
+static int get_starttime(int pid)
+{
+       char file[20];
+       char buffer[1024];
+       sprintf(file, "/proc/%d/stat", pid);
+       FILE *fd = fopen(file, "r");
+               if (fd==NULL) return -1;
+       fgets(buffer, sizeof(buffer), fd);
+       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;
+}
+
+// detects whether a process is a kernel thread or not
+static int is_kernel_thread(int 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;
+       fgets(buffer, sizeof(buffer), fd);
+       ret = strncmp(buffer,"0 0 0",3)==0;
+       fclose(fd);
+       return ret;
+}
+
+// returns 1 if pid is a user process, 0 otherwise
+static int process_exists(int 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 0;
+       fgets(buffer, sizeof(buffer), fd);
+       ret = strncmp(buffer,"0 0 0",3)!=0;
+       fclose(fd);
+       return ret;
+}
+
+/* PID HASH FUNCTIONS */
+
+static int hash_process(struct process_family *f, struct process *p)
+{
+       int ret;
+       struct list **l = &(f->hashtable[pid_hashfn(p->pid)]);
+       if (*l == NULL) {
+               //there is no process in this hashtable item
+               //allocate the list
+               *l = 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));
+                       ret = 1;
+               }
+               else {
+                       //add new process
+                       add_elem(*l, p);
+                       ret = 0;
+                       f->count++;
+               }
+       }
+       return ret;
+}
+
+static void unhash_process(struct process_family *f, int pid) {
+       //remove process from hashtable
+       struct list **l = &(f->hashtable[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 *find_process(struct process_family *f, int pid)
+{
+       struct list **l = &(f->hashtable[pid_hashfn(pid)]);
+       return (*l != NULL) ? (struct process*)locate_elem(*l, &pid) : NULL;
+}
+
+/*
+static int is_member(struct process_family *f, int pid) {
+       struct process *p = find_process(f, pid);
+       return (p!=NULL && p->member);
+}
+
+static int exists(struct process_family *f, int pid) {
+       struct process *p = find_process(f, pid);
+       return p!=NULL;
+}
+*/
+
+/* PROCESS ITERATOR STUFF */
+
+// creates an object that browse all running processes
+static int init_process_iterator(struct process_iterator *i) {
+       //open a directory stream to /proc directory
+       if ((i->dip = opendir("/proc")) == NULL) {
+               perror("opendir");
+               return -1;
+       }
+       return 0;
+}
+
+// reads the next user process from /process
+// automatic closing if the end of the list is reached
+static int read_next_process(struct process_iterator *i) {
+       //read in from /proc and seek for process dirs
+       int pid = 0;
+       while ((i->dit = readdir(i->dip)) != NULL) {
+               pid = atoi(i->dit->d_name);
+               if (pid<=0 || is_kernel_thread(pid))
+                       continue;
+               //return the first found process
+               break;
+       }
+       if (pid == 0) {
+               //no more processes, release resources
+               closedir(i->dip);
+       }
+       return pid;
+}
+
+/* PUBLIC FUNCTIONS */
+
+// searches for all the processes derived from father and stores them
+// in the process family struct
+int create_process_family(struct process_family *f, int father)
+{
+       //process list initialization
+       init_list(&(f->members), 4);
+       //hashtable initialization
+       memset(&(f->hashtable), 0, sizeof(f->hashtable));
+       f->count = 0;
+       f->father = father;
+       //process iterator
+       struct process_iterator iter;
+       init_process_iterator(&iter);
+       int pid = 0;
+       while ((pid = read_next_process(&iter))) {
+               //check if process belongs to the family
+               int ppid = pid;
+               //TODO: optimize (add also parents)
+               while(ppid!=1 && ppid!=father) {
+                       ppid = getppid_of(ppid);
+               }
+               //allocate and insert the process
+               struct process *p = malloc(sizeof(struct process));
+               p->pid = pid;
+               p->starttime = get_starttime(pid);
+               if (ppid==1) {
+                       p->member = 0;
+               }
+               else if (pid != getpid()) {
+                       //add to members (only if it's not the current cpulimit process!)
+                       p->member = 1;
+                       add_elem(&(f->members), p);
+               }
+               //init history
+               p->history = malloc(sizeof(struct process_history));
+               process_init(p->history, pid);
+               //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 check_new_members(struct process_family *f)
+{
+       int ret = 0;
+       //process iterator
+       struct process_iterator iter;
+       init_process_iterator(&iter);
+       int pid = 0;
+       while ((pid = read_next_process(&iter))) {
+               struct process *newp = find_process(f, pid);
+               if (newp != NULL) continue; //already known
+               //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=find_process(f, ppid))==NULL) {
+                       ppid = getppid_of(ppid);
+               }
+               if (ancestor == NULL) {
+                       //this should never happen! if does, find and correct the bug
+                       printf("Fatal bug! Process %d is without parent\n", pid);
+                       exit(1);
+               }
+               //allocate and insert the process
+               struct process *p = malloc(sizeof(struct process));
+               p->pid = pid;
+               p->starttime = get_starttime(pid);
+               p->member = 0;
+               if (ancestor->member) {
+                       //add to members
+                       p->member = 1;
+                       add_elem(&(f->members), p);
+                       ret++;
+               }
+               //init history
+               p->history = malloc(sizeof(struct process_history));
+               process_init(p->history, pid);
+               //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, int 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;
+               destroy_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->hashtable) / sizeof(struct process*);
+       for (i=0; i<size; i++) {
+               if (f->hashtable[i] != NULL) {
+                       //free() history for each process
+                       struct list_node *node = NULL;
+                       for (node=f->hashtable[i]->first; node!=NULL; node=node->next) {
+                               struct process *p = (struct process*)(node->data);
+                               free(p->history);
+                               p->history = NULL;
+                       }
+                       destroy_list(f->hashtable[i]);
+                       free(f->hashtable[i]);
+                       f->hashtable[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(int pid)
+{
+       if (process_exists(pid))
+               return (kill(pid,SIGCONT)==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)
+{
+       //the name of /proc/pid/exe symbolic link pointing to the executable file
+       char exelink[20];
+       //the name of the executable file
+       char exepath[PATH_MAX+1];
+       //whether the variable process is the absolute path or not
+       int is_absolute_path = process[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);
+       int pid = 0;
+       while ((pid = read_next_process(&iter))) {
+               //read the executable link
+               sprintf(exelink,"/proc/%d/exe",pid);
+               int size = readlink(exelink, exepath, sizeof(exepath));
+               if (size>0) {
+                       found = 0;
+                       if (is_absolute_path && strncmp(exepath, process, size)==0 && size==strlen(process)) {
+                               //process found
+                               found = 1;
+                       }
+                       else {
+                               //process found
+                               if (strncmp(exepath + size - strlen(process), process, strlen(process))==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/procutils.h b/procutils.h
new file mode 100644 (file)
index 0000000..87f0395
--- /dev/null
@@ -0,0 +1,89 @@
+/**
+ *
+ * cpulimit - a cpu limiter for Linux
+ *
+ * Copyright (C) 2005-2008, by:  Angelo Marletta <marlonx80@hotmail.com>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+#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
+       int father;
+       //members list (father process is the first element)
+       struct list members;
+       //non-members list
+       //hashtable with all the processes (array of struct list of struct process)
+       struct list *hashtable[PIDHASH_SZ];
+       //total process count
+       int count;
+};
+
+// process descriptor
+struct process {
+       int pid;
+       int starttime;
+       int member;
+       struct process_history *history;
+};
+
+// object to enumerate running processes
+struct process_iterator {
+       DIR *dip;
+       struct dirent *dit;     
+};
+
+// searches for all the processes derived from father and stores them
+// in the process family struct
+int create_process_family(struct process_family *f, int 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 check_new_members(struct process_family *f);
+
+// removes a process from the family by its pid
+void remove_process_from_family(struct process_family *f, int 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);
+
+// searches a process given its pid
+// returns the pid, or 0 if it's not found
+int look_for_process_by_pid(int pid);
+
+#endif
diff --git a/ptest.c b/ptest.c
new file mode 100644 (file)
index 0000000..9fadb6f
--- /dev/null
+++ b/ptest.c
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include "procutils.h"
+
+//simple program to test cpulimit
+
+int main()
+{
+       printf("PID %d\n", getpid());
+       int i;
+       for (i=0;i<10;i++) {
+               if (fork()>0){
+                       printf("PID %d\n", getpid());
+                       while(1);
+               }
+       }
+       return 0;
+}