Skip to contentSkip to conversation

Enhanced Linux System Accounting

PATCH New Guillaume Thouvenin 1 participant 1 message May 12, 2004

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Enhanced Linux System Accounting

Hello,

This is a new patch "patch-2.6.6-elsa" for 2.6.6 kernel. It can be downloaded from:
http://sourceforge.net/project/showfiles.php?group_id=105806

I also attach it at the end of this file. In this patch we rewrite
functions and we made a clean separation between different parts of ELSA (thank you very much to Chris Wright for its usefull comments). This patch allows you to test BSD-like accounting for a group of process. Informations are dumped in /var/log/bank (need to change that). "BANK" can be manipulated via ioctl() and /dev/elsacct. A C program, called "elsa_cmd.c", that allows to add/remove process to/from a bank can be downloaded from the CVS:
http://cvs.sourceforge.net/viewcvs.py/elsa/tests/

We need to improve lock between the main structure called "elsa_broot"
and add new accounting informations. Our goal is to provide an environment that can support System V accounting and a unify interface from existing accounting tools (like sar) to be used by existing Linux administrative tools (like WebMin).

Any feedbacks, any comments are welcome
Best,
Guillaume

drivers/elsacct/bank.c +607 -0
diff -uprN -X elsa_import/dontdiff linux-2.6.6/drivers/elsacct/bank.c linux-2.6.6-elsa/drivers/elsacct/bank.c
@@ -0,0 +1,607 @@
+/*
+ *  driver/elsacct/bank.c
+ * 
+ *  ELSA - Enhanced Linux System Accounting
+ *  Guillaume Thouvenin - 26/04/2004
+ *
+ *  This file implements Enhanced Linux System Accounting. It 
+ *  provides structure and functions to manipulate "BANK". 
+ *
+ * 
+ *  This code is licenced under GPL.
+ */
+
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/seq_file.h>
+#include <linux/module.h>
+
+#include <linux/bank.h>
+
+#include <asm/semaphore.h>
+#include <asm/bug.h>
+
+ /*********************************************
+ * structures and macros used to manage banks *
+ *********************************************/
+
+#ifdef CONFIG_BANK_DEBUG
+#define dprintk(format...) printk(format)
+#else
+#define dprintk(format)
+#endif
+
+/* It's the head on the list of banks */
+static struct bank_root elsa_broot = BANK_ROOT_INIT(elsa_broot);
+
+/* semaphore that protect access to bank id */
+static DECLARE_MUTEX(elsa_bid_sem);
+//SPIN_LOCK_UNLOCKED(elsalist_lock);
+
+ /********************************************************************
+ * Following functions can be use by a module (=> they are exported) *
+ ********************************************************************/
+
+/**
+ * elsa_bank_alloc - Allocates a new bank
+ * @fn : callback used when a data or a bank are destroyed
+ * @info : generic information attached to a bank (used for accounting) 
+ *
+ * Allocates a new bank and returns the new created bank.
+ * If an error is encountered NULL is returned.
+ *
+ * Here are different steps of the operation
+ *
+ *   1) Allocate space for the new bank
+ *   2) Give it an identifier
+ *   3) Initialize the head of the list of items (items will point to process)
+ *   4) Add it to the list of available bank
+ */
+struct elsa_bank
+*elsa_bank_alloc(int (*fn) (int, struct elsa_bank *, struct elsa_data *),
+		 void *info)
+{
+	struct elsa_bank *b;
+	int bid;
+
+	/* allocate space for the new bank */
+	b = (struct elsa_bank *)kmalloc(sizeof(struct elsa_bank), GFP_KERNEL);
+	if (!b) {
+		printk("elsa_bank_alloc: cannot allocate space\n");
+		return NULL;
+	}
+
+	/* 
+	 * give it an id 
+	 * Currently we only used next_bid field but this mechnism 
+	 * can give problems if we use many banks and the bid go through
+	 * its maximum  
+	 */
+	down(&elsa_bid_sem);
+	bid = elsa_broot.next_bid++;
+	up(&elsa_bid_sem);
+
+	if (bid <= 0) {
+		/* There is no available id == ERROR */
+		kfree(b);
+		printk("elsa_bank_alloc: can not find bank identifier\n");
+		return NULL;
+	} else {
+		b->bid = bid;
+	}
+
+	/* Initialize fields */
+	b->info = info;
+	b->callback = fn;
+	INIT_LIST_HEAD(&(b->data_head));
+
+	/* add the new bank to the list of banks --- NEED LOCK */
+	list_add(&(b->bank_list), &(elsa_broot.bank_head));
+
+	dprintk("elsa_bank_alloc: bank #%d created\n", b->bid);
+
+	return b;
+}
+
+/**
+ * elsa_data_alloc - Allocates a new data
+ * 
+ * Allocates a new data and returns the new created item.
+ * If an error is encountered NULL is returned.
+ *
+ * Here are different steps of the operation
+ *
+ *   1) Allocate space for the new data
+ *   2) Initialize different fields
+ */
+struct elsa_data *elsa_data_alloc(void)
+{
+	struct elsa_data *d;
+
+	/* allocate space for the new data */
+	d = (struct elsa_data *)kmalloc(sizeof(struct elsa_data), GFP_KERNEL);
+	if (!d) {
+		printk("elsa_data_alloc: cannot allocate space\n");
+		return NULL;
+	}
+
+	/* Initialize fields */
+	d->bid = 0;
+	d->process = NULL;
+	INIT_LIST_HEAD(&(d->data_list));
+	INIT_LIST_HEAD(&(d->bank_list));
+
+	return d;
+}
+
+/**
+ * elsa_bank_free - Frees space occupied by a bank.
+ * @bank: a pointer to the bank to delete
+ * 
+ * Removes a bank and returns the identifiers of the removed bank.
+ * When this function is called bank *MUST* be empty.
+ *
+ * Here are different steps of the operation
+ *
+ *   1) Write accounting information
+ *   2) Remove it from the list of banks
+ *   3) Free space used by bank
+ */
+void elsa_bank_free(struct elsa_bank *bank)
+{
+	BUG_ON(bank == NULL);
+	BUG_ON(!list_empty(&bank->data_head));
+
+	/* Before deleting the bank, we can do some action */
+	/* callback */
+	if (bank->callback) {
+		(bank->callback) (ELSA_BANK_CALLBACK, bank, NULL);
+	}
+
+	/* remove bank from the bank's list */
+	list_del(&(bank->bank_list));
+	dprintk("elsa_bank_free: bank #%d removed from the list\n", bank->bid);
+	kfree(bank);
+}
+
+/**
+ * elsa_data_free - Free data
+ * @data: data to be removed
+ * 
+ * Frees memory space used by data. 
+ */
+void elsa_data_free(struct elsa_data *data)
+{
+	struct elsa_bank *b = NULL;
+
+	BUG_ON(data == NULL);
+
+	/*
+	 * if data->bid is equal to 0, it means that an error occured between
+	 * data's allocation and data's addition in a bank. So, it isn't inserted
+	 * in any list and there is no need to call the "callback"
+	 */
+	if (data->bid != 0) {
+		/* callback, we are in the child so callback code */
+		b = elsa_get_bank(data->bid);
+		if (b && b->callback) {
+			(b->callback) (ELSA_DATA_CALLBACK, b, data);
+		}
+
+		list_del(&data->data_list);
+
+		/* Need access to head_list in task_struct */
+		write_lock_irq(&tasklist_lock);
+		list_del(&data->bank_list);
+		write_unlock_irq(&tasklist_lock);
+
+		dprintk
+		    ("elsa_data_free: process #%d removed from the bank#%d\n",
+		     data->process->pid, b->bid);
+	}
+
+	kfree(data);
+}
+
+/**
+ * elsa_bank_add - Add a process to a given bank
+ * @b  : pointer to a bank 
+ * @p  : pointer to a process
+ *
+ * add a data to a bank by setting lists. Other fields are unchanged
+ *
+ * If an error is encountered, a negative value is returned. 
+ * It can not return 0.
+ *
+ * Here are steps to perform
+ *  
+ *   1) Check arguments
+ *   2) Check if process is already in the bank
+ *   3) Initialize its fields
+ *   4) Return BID
+ */
+int elsa_bank_add(struct elsa_bank *b, struct elsa_data *d)
+{
+	if (!b || !d || !(d->process)) {
+		dprintk("elsa_bank_add: Wrong parameters\n");
+		return -EINVAL;
+	}
+
+	/* Bank ID cannot be equal to 0 */
+	BUG_ON(b->bid == 0);
+
+	/* check if process is already present */
+	if (elsa_process_present(d->process, b))
+		/* Process already in the bank, so we can not add it */
+		return -EPERM;
+
+	/* Set BID */
+	d->bid = b->bid;
+
+	/* Add data in the list of bank's data */
+	list_add(&(d->data_list), &(b->data_head));
+
+	/* Add data in the list of process's bank */
+	write_lock_irq(&tasklist_lock);
+	list_add(&(d->bank_list), &(d->process->bank_head));
+	write_unlock_irq(&tasklist_lock);
+
+	dprintk("elsa_bank_add: Add process #%d to bank #%d\n", d->process->pid,
+		b->bid);
+
+	return b->bid;
+}
+
+/**
+ * elsa_bank_remove - Remove a process to a given bank
+ * @b: pointer to the bank
+ * @d: pointer to the process
+ *
+ * Removes a given process from a given bank. 
+ * Return 0 if succedeed, negative value otherwise
+ */
+int elsa_bank_remove(struct elsa_data *d)
+{
+	int b_empty = 0;
+	struct elsa_bank *b = NULL;	/*  Used if d is the last 
+					   item of the bank */
+
+	if (!d)
+		return -EINVAL;
+
+	/* bank will be empty if d is the last item in the bank */
+	if (d->data_list.next == d->data_list.prev) {
+		b_empty = 1;
+		b = elsa_get_bank(d->bid);
+	}
+
+	/* kfree memory */
+	elsa_data_free(d);
+
+	/* 
+	 * We must free bank after data otherwise we will miss 
+	 * accounting informations 
+	 */
+	if (b_empty)
+		elsa_bank_free(b);
+
+	return 0;
+}
+
+/**
+ * elsa_bank_clean - Removes all datas in a given bank
+ * @b: pointer to the bank to be removed
+ *
+ * Removes data found in a bank given as paramater. It go through the
+ * list, removes link and free space occupied by the data.
+ * Return 0 if succedeed, negative value otherwise
+ */
+int elsa_bank_clean(struct elsa_bank *b)
+{
+	if (!b) {
+		/* bank doesn't exist */
+		dprintk("elsa_bank_clean: bank doesn't exist\n");
+		return -EAGAIN;
+	}
+
+	/* release all datas and remove the bank */
+	while (!list_empty(&(b->data_head))) {
+		/* get pointer to the first element in the list */
+		struct elsa_data *d = list_entry(b->data_head.next,
+						 struct elsa_data, data_list);
+		elsa_data_free(d);
+	}
+
+	elsa_bank_free(b);
+
+	return 0;
+}
+
+/**
+ * elsa_process_remove - Remove a process from all banks that it belongs
+ * @p: pointer to the process to be removed
+ *
+ * Removes process from all banks
+ * Return 0 if succedeed, negative value otherwise
+ */
+int elsa_process_remove(struct task_struct *p)
+{
+
+	if (!p) {
+		/* bank doesn't exist */
+		dprintk("elsa_process_remove: process doesn't exist\n");
+		return -EAGAIN;
+	}
+
+	/* release all datas and remove the bank */
+	while (!list_empty(&(p->bank_head))) {
+		/* get pointer to the first element in the list */
+		struct elsa_data *d = list_entry(p->bank_head.next,
+						 struct elsa_data, bank_list);
+		elsa_bank_remove(d);
+	}
+
+	return 0;
+}
+
+/**
+ * elsa_get_bank - Returns a pointer to a bank
+ * @bid: The identifier of a bank
+ * 
+ * Finds the bank with given ID in the list of banks
+ */
+struct elsa_bank *elsa_get_bank(unsigned int bid)
+{
+	struct list_head *entry;
+
+	list_for_each(entry, &(elsa_broot.bank_head)) {
+		struct elsa_bank *b =
+		    list_entry(entry, struct elsa_bank, bank_list);
+		if (b->bid == bid)
+			return b;
+	}
+
+	return NULL;
+}
+
+/**
+ * elsa_get_data - Returns a pointer to the container of a 
+ *                 given pid in a given bank.
+ * @pid: The identifier of the process
+ * 
+ * Find the data that holds process #PID in a given bank. If
+ * not found, NULL is returned.
+ */
+struct elsa_data *elsa_get_data(unsigned int pid, unsigned int bid)
+{
+	struct list_head *entry;
+	struct task_struct *p;
+
+	read_lock(&tasklist_lock);
+	p = find_task_by_pid(pid);
+	read_unlock(&tasklist_lock);
+
+	list_for_each(entry, &(p->bank_head)) {
+		struct elsa_data *d =
+		    list_entry(entry, struct elsa_data, bank_list);
+		if (d->bid == bid)
+			/* Got it */
+			return d;
+	}
+
+	return NULL;
+}
+
+/**
+ * elsa_process_present - Returns 1 if process is in the bank
+ * @p: the process identifier to look for
+ * @b: the bank in which we must look.
+ * 
+ * Return 1 if process is present in a bank, 0 otherwise
+ */
+int elsa_process_present(struct task_struct *p, struct elsa_bank *b)
+{
+	struct list_head *entry;
+
+	list_for_each(entry, &(b->data_head)) {
+		struct elsa_data *d =
+		    list_entry(entry, struct elsa_data, data_list);
+		if (d->process == p) {
+			/* Found it */
+			return 1;
+		}
+	}
+
+	/* Process not found in b */
+	return 0;
+}
+
+/* memory management */
+EXPORT_SYMBOL(elsa_bank_alloc);
+EXPORT_SYMBOL(elsa_data_alloc);
+EXPORT_SYMBOL(elsa_bank_free);
+EXPORT_SYMBOL(elsa_data_free);
+
+/* data manipulation */
+EXPORT_SYMBOL(elsa_bank_add);
+EXPORT_SYMBOL(elsa_bank_remove);
+EXPORT_SYMBOL(elsa_bank_clean);
+EXPORT_SYMBOL(elsa_process_remove);
+
+/* get informations */
+EXPORT_SYMBOL(elsa_get_bank);
+EXPORT_SYMBOL(elsa_get_data);
+EXPORT_SYMBOL(elsa_process_present);
+
+ /*****************************************************************************
+ * Following functions are called from kernel function.                       *
+ *   elsa_copy_parent_bank() is used when child is created (kernel/fork.c)  *
+ *****************************************************************************/
+
+/**
+ * elsa_copy_parent_bank - Add a given process to the same banks 
+ *                          as another one
+ * @from: Process from where we will copy information
+ * @to: Process to where we will copy information 
+ * 
+ * Goes through the banks to which "from" process belong and add 
+ * the process in those banks. 
+ *
+ * It is used when doing a fork (kernel/fork.c - copy_process()). 
+ * This function is used by the kernel to update child's banks.
+ */
+void elsa_copy_parent_bank(struct task_struct *from, struct task_struct *to)
+{
+	struct list_head *entry;
+
+	/* 
+	 * First, initialize to->bank_head otherwise, if 
+	 * from->bank_head is NULL it won't be initialize
+	 */
+	write_lock_irq(&tasklist_lock);
+	INIT_LIST_HEAD(&(to->bank_head));
+	write_unlock_irq(&tasklist_lock);
+
+	list_for_each(entry, &(from->bank_head)) {
+		struct elsa_bank *b;
+		struct elsa_data *d_from;
+		struct elsa_data *d_to;
+
+		dprintk("elsa_copy_parent_bank: from pid#%d to pid#%d\n",
+			from->pid, to->pid);
+
+		d_from = list_entry(entry, struct elsa_data, bank_list);
+		if (!d_from)
+			return;
+
+		/* allocat space to new data */
+		d_to = elsa_data_alloc();
+		if (!d_to) {
+			printk("elsa_copy_parent_bank: Cannot create data\n");
+			return;
+		}
+
+		d_to->bid = d_from->bid;
+		d_to->process = to;
+
+		/* d_to->process is set so we can call elsa_bank_add */
+		b = elsa_get_bank(d_from->bid);
+		if (!b) {
+			elsa_data_free(d_to);
+			printk
+			    ("elsa_copy_parent_bank: Destination not found\n");
+			return;
+		}
+
+		if (b->bid != elsa_bank_add(b, d_to)) {
+			elsa_data_free(d_to);
+			printk("elsa_copy_parent_bank: Copy error\n");
+			return;
+		}
+	}
+}
+
+ /*********************************
+ * functions used to manage /proc *
+ *                                *
+ * The entry is /proc/bankinfo    *
+ *********************************/
+
+/***
+ * Add an entry in /proc to get informations concerning
+ * banks. This entry is called /proc/bankinfo
+ ****/
+#ifdef CONFIG_PROC_FS
+static void *b_start(struct seq_file *m, loff_t * pos)
+{
+	loff_t n = *pos;
+	struct list_head *p;
+
+	/* Header is displaying just during the first called */
+	if (!n) {
+		seq_puts(m, "# - bankinfo -\n");
+		seq_puts(m, "# bankid:\t<process> <process> ...\n");
+	}
+
+	if (list_empty(&elsa_broot.bank_head))
+		return NULL;
+
+	p = elsa_broot.bank_head.next;
+	while (n--) {
+		p = p->next;
+		if (p == &elsa_broot.bank_head)
+			return NULL;
+	}
+
+	/* we return a pointer to the data in the bank */
+	return list_entry(p, struct elsa_bank, bank_list);
+}
+
+static void b_stop(struct seq_file *m, void *v)
+{
+}
+
+static void *b_next(struct seq_file *m, void *v, loff_t * pos)
+{
+	struct elsa_bank *bank = v;
+
+	++*pos;
+	return bank->bank_list.next == &elsa_broot.bank_head ? NULL :
+	    list_entry(bank->bank_list.next, struct elsa_bank, bank_list);
+}
+
+static int show_bankinfo(struct seq_file *m, void *v)
+{
+	struct elsa_bank *bank = v;
+	struct elsa_data *data;
+
+	if (!bank) {
+		seq_printf(m, "There is no banks\n");
+	} else {
+		/* display bank identifier */
+		seq_printf(m, "%d:", bank->bid);
+		/* add a tabulation */
+		seq_printf(m, "\t");
+		/* display list of processus */
+		if (list_empty(&(bank->data_head))) {
+			seq_printf(m, "Empty");
+		} else {
+			list_for_each_entry(data, &(bank->data_head), data_list)
+			    seq_printf(m, "%d ", data->process->pid);
+		}
+		/* add EOL */
+		seq_printf(m, "\n");
+	}
+
+	return 0;
+}
+
+/* bankinfo_op - iterator that generates /proc/bankinfo
+ *
+ * Output layout:
+ * bankID	pid ...
+ */
+struct seq_operations bankinfo_op = {
+	.start = b_start,
+	.stop = b_stop,
+	.next = b_next,
+	.show = show_bankinfo,
+};
+
+int bankinfo_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &bankinfo_op);
+}
+
+struct file_operations proc_bankinfo_ops = {
+	.open = bankinfo_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = seq_release,
+};
+
+EXPORT_SYMBOL(proc_bankinfo_ops);
+
+#endif
drivers/elsacct/elsacct.c +438 -0

438 lines not on this page · raw message

drivers/elsacct/Kconfig +61 -0

61 lines not on this page · raw message

drivers/elsacct/Makefile +5 -0
diff -uprN -X elsa_import/dontdiff linux-2.6.6/drivers/elsacct/Makefile linux-2.6.6-elsa/drivers/elsacct/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the Enhanced Linux System Accounting
+#
+obj-$(CONFIG_BANK)	+= bank.o
+obj-$(CONFIG_ELSACCT)	+= elsacct.o
drivers/Kconfig +2 -0
diff -uprN -X elsa_import/dontdiff linux-2.6.6/drivers/Kconfig linux-2.6.6-elsa/drivers/Kconfig
@@ -4,6 +4,8 @@ menu "Device Drivers"
 
 source "drivers/base/Kconfig"
 
+source "drivers/elsacct/Kconfig"
+
 source "drivers/mtd/Kconfig"
 
 source "drivers/parport/Kconfig"
drivers/Makefile +1 -0
diff -uprN -X elsa_import/dontdiff linux-2.6.6/drivers/Makefile linux-2.6.6-elsa/drivers/Makefile
@@ -50,3 +50,4 @@ obj-$(CONFIG_MCA)		+= mca/
 obj-$(CONFIG_EISA)		+= eisa/
 obj-$(CONFIG_CPU_FREQ)		+= cpufreq/
 obj-y				+= firmware/
+obj-y				+= elsacct/
include/linux/bank.h +164 -0

164 lines not on this page · raw message

include/linux/elsacct.h +55 -0

55 lines not on this page · raw message

include/linux/init_task.h +1 -0
diff -uprN -X elsa_import/dontdiff linux-2.6.6/include/linux/init_task.h linux-2.6.6-elsa/include/linux/init_task.h
@@ -112,6 +112,7 @@ extern struct group_info init_groups;
 	.proc_lock	= SPIN_LOCK_UNLOCKED,				\
 	.switch_lock	= SPIN_LOCK_UNLOCKED,				\
 	.journal_info	= NULL,						\
+	.bank_head	= LIST_HEAD_INIT(tsk.bank_head),		\
 }
 
 
include/linux/sched.h +3 -0
diff -uprN -X elsa_import/dontdiff linux-2.6.6/include/linux/sched.h linux-2.6.6-elsa/include/linux/sched.h
@@ -504,6 +504,9 @@ struct task_struct {
 
 	unsigned long ptrace_message;
 	siginfo_t *last_siginfo; /* For ptrace use.  */
+
+/* List of BANK to which the process belong -- Used by ELSA */
+	struct list_head bank_head;
 };
 
 static inline pid_t process_group(struct task_struct *tsk)
kernel/exit.c +1 -0
diff -uprN -X elsa_import/dontdiff linux-2.6.6/kernel/exit.c linux-2.6.6-elsa/kernel/exit.c
@@ -59,6 +59,7 @@ repeat: 
 	atomic_dec(&p->user->processes);
 	spin_lock(&p->proc_lock);
 	proc_dentry = proc_pid_unhash(p);
+	elsa_process_remove(p);
 	write_lock_irq(&tasklist_lock);
 	if (unlikely(p->ptrace))
 		__ptrace_unlink(p);
kernel/fork.c +13 -0
diff -uprN -X elsa_import/dontdiff linux-2.6.6/kernel/fork.c linux-2.6.6-elsa/kernel/fork.c
@@ -1011,6 +1011,19 @@ struct task_struct *copy_process(unsigne
 	INIT_LIST_HEAD(&p->ptrace_children);
 	INIT_LIST_HEAD(&p->ptrace_list);
 
+        /*
+         * Child is in the same BANK as parent. So we copy the list of banks
+         * from current to p. We can not just counting reference on the structure
+         * because the may evoluate in differents way. I think it's easier to
+         * do like this but maybe I'm wrong... -- guillaume
+         *
+         * We put that call outside lock_irq because elsa_copy_parent_bank
+         * uses kmalloc(GFP_KERNEL). Therefore, as we modify task_struct,
+         * we manage lock_irq ourselves. -- guillaume
+         *
+         */
+        elsa_copy_parent_bank(current, p);
+	
 	/* Need tasklist lock for parent etc handling! */
 	write_lock_irq(&tasklist_lock);
 	/*
Makefile +1 -1
diff -uprN -X elsa_import/dontdiff linux-2.6.6/Makefile linux-2.6.6-elsa/Makefile
@@ -1,7 +1,7 @@
 VERSION = 2
 PATCHLEVEL = 6
 SUBLEVEL = 6
-EXTRAVERSION =
+EXTRAVERSION = -elsa
 NAME=Zonked Quokka
 
 # *DOCUMENTATION*