From 4229fbd380446489c6d84d94169b4ad2437e6b26 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Sun, 6 Dec 2009 17:17:27 +0100 Subject: [PATCH] introduce a generic priority queue implementation --- src/Makefile.am | 4 +- src/prioqueue.c | 207 ++++++++++++++++++++++++++++++++++++++++++++++++ src/prioqueue.h | 66 +++++++++++++++ src/top.c | 175 ++++++++++++++-------------------------- 4 files changed, 334 insertions(+), 118 deletions(-) create mode 100644 src/prioqueue.c create mode 100644 src/prioqueue.h diff --git a/src/Makefile.am b/src/Makefile.am index 397ffb08..61353bd7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -53,8 +53,8 @@ mandatory_sources = colours.c colours.h combine.c combine.h common.c common.h \ conky.cc conky.h core.cc core.h diskio.c diskio.h entropy.c entropy.h \ exec.c exec.h fs.c fs.h logging.h mail.c mail.h mixer.c mixer.h net_stat.c \ net_stat.h template.c template.h timed_thread.c timed_thread.h mboxscan.c \ - mboxscan.h read_tcp.c read_tcp.h scroll.c scroll.h specials.c \ - specials.h tailhead.c tailhead.h temphelper.c temphelper.h \ + mboxscan.h prioqueue.c prioqueue.h read_tcp.c read_tcp.h scroll.c scroll.h \ + specials.c specials.h tailhead.c tailhead.h temphelper.c temphelper.h \ text_object.c text_object.h timeinfo.c timeinfo.h top.c top.h algebra.c \ algebra.h proc.c proc.h user.c user.h diff --git a/src/prioqueue.c b/src/prioqueue.c new file mode 100644 index 00000000..0c9d9927 --- /dev/null +++ b/src/prioqueue.c @@ -0,0 +1,207 @@ +/* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- + * vim: ts=4 sw=4 noet ai cindent syntax=c + * + * prioqueue: a simple priority queue implementation + * + * The queue organises it's data internally using a doubly linked + * list, into which elements are inserted at the right position. This + * is definitely not the best algorithm for a priority queue, but it + * fits best for the given purpose, i.e. the top process sorting. + * This means we have a rather little amount of total elements (~200 + * on a normal system), which are to be inserted into a queue of only + * the few top-most elements (10 at the current state). Additionally, + * at each update interval, the queue is drained completely and + * refilled from scratch. + * + * Copyright (C) 2009 Phil Sutter + * + * Initially based on the former implementation of sorted processes in + * top.c, Copyright (C) 2005 David Carter + * + * 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 3 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, see . + * + */ + +#include /* INT_MAX */ +#include +#include + +struct prio_elem { + struct prio_elem *next, *prev; + void *data; +}; + +struct prio_queue { + /* Compare a and b. Return: + * <0 if a should come before b, + * >0 if b should come before a, + * 0 if don't care */ + int (*compare)(void *a, void *b); + + /* Free element payload. Called when dropping elements. */ + void (*free)(void *a); + + /* Maximum size of queue. The first + * elements in the list take precedence. */ + int max_size; + + /* The pointers to the actual list. */ + struct prio_elem *head, *tail; + + /* The current number of elements in the list. */ + int cur_size; +}; + +/* nop callback to save us from conditional calling */ +static void pq_free_nop(void *a) { (void)a; } + +struct prio_queue *init_prio_queue(void) +{ + struct prio_queue *retval; + + retval = malloc(sizeof(struct prio_queue)); + memset(retval, 0, sizeof(struct prio_queue)); + + /* use pq_free_nop by default */ + retval->free = &pq_free_nop; + + /* Default to maximum possible size as restricted + * by the used data type. This also saves us from + * checking if caller has set this field or not. */ + retval->max_size = INT_MAX; + + return retval; +} + +void pq_set_compare(struct prio_queue *queue, int (*pqcompare)(void *a, void *b)) +{ + if (pqcompare) + queue->compare = pqcompare; +} + +void pq_set_free(struct prio_queue *queue, void (*pqfree)(void *a)) +{ + if (pqfree) + queue->free = pqfree; +} + +void pq_set_max_size(struct prio_queue *queue, int max_size) +{ + if (max_size >= 0) + queue->max_size = max_size; +} + +int pq_get_cur_size(struct prio_queue *queue) +{ + return queue->cur_size; +} + +static struct prio_elem *init_prio_elem(void *data) +{ + struct prio_elem *retval; + + retval = malloc(sizeof(struct prio_elem)); + memset(retval, 0, sizeof(struct prio_elem)); + + retval->data = data; + return retval; +} + +void insert_prio_elem(struct prio_queue *queue, void *data) +{ + struct prio_elem *cur; + + /* queue->compare is a must-have */ + if (!queue->compare) + return; + + /* empty queue, insert the first item */ + if (!queue->cur_size) { + queue->cur_size++; + queue->head = queue->tail = init_prio_elem(data); + return; + } + + /* short-cut 1: new item is lower than all others */ + if (queue->compare(queue->tail->data, data) <= 0) { + if (queue->cur_size < queue->max_size) { + queue->cur_size++; + queue->tail->next = init_prio_elem(data); + queue->tail->next->prev = queue->tail; + queue->tail = queue->tail->next; + } else /* list was already full */ + (*queue->free)(data); + return; + } + + /* short-cut 2: we have a new maximum */ + if (queue->compare(queue->head->data, data) >= 0) { + queue->cur_size++; + queue->head->prev = init_prio_elem(data); + queue->head->prev->next = queue->head; + queue->head = queue->head->prev; + goto check_cur_size; + } + + /* find the actual position if short-cuts failed */ + for (cur = queue->head->next; cur; cur = cur->next) { + if (queue->compare(cur->data, data) >= 0) { + queue->cur_size++; + cur->prev->next = init_prio_elem(data); + cur->prev->next->prev = cur->prev; + cur->prev->next->next = cur; + cur->prev = cur->prev->next; + break; + } + } + +check_cur_size: + /* drop the lowest item if queue overrun */ + if (queue->cur_size > queue->max_size) { + queue->cur_size--; + queue->tail = queue->tail->prev; + (*queue->free)(queue->tail->next->data); + free(queue->tail->next); + queue->tail->next = NULL; + } +} + +void *pop_prio_elem(struct prio_queue *queue) +{ + struct prio_elem *tmp; + void *data; + + if (queue->cur_size <= 0) + return NULL; + + tmp = queue->head; + data = tmp->data; + + queue->head = queue->head->next; + queue->cur_size--; + if (queue->head) + queue->head->prev = NULL; + else /* list is now empty */ + queue->tail = NULL; + + free(tmp); + return data; +} + +void free_prio_queue(struct prio_queue *queue) +{ + void *data; + while((data = pop_prio_elem(queue))) + (*queue->free)(data); + free(queue); +} diff --git a/src/prioqueue.h b/src/prioqueue.h new file mode 100644 index 00000000..764be23b --- /dev/null +++ b/src/prioqueue.h @@ -0,0 +1,66 @@ +/* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- + * vim: ts=4 sw=4 noet ai cindent syntax=c + * + * prioqueue: a simple priority queue implementation + * + * Copyright (C) 2009 Phil Sutter + * + * Initially based on the former implementation of sorted processes in + * top.c, Copyright (C) 2005 David Carter + * + * 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 3 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, see . + * + */ + +#ifndef _PRIOQUEUE_H +#define _PRIOQUEUE_H + +/* forward-define for private data */ +struct prio_queue; + +/* typedef for a distinct prioqueue object */ +typedef struct prio_queue *prio_queue_t; + +/* initialise a prioqueue object (mandatory) */ +prio_queue_t init_prio_queue(void); + +/* set the compare function (mandatory) + * (*compare) shall return: + * <0 if a should come before b, + * >0 if b should come before a, + * 0 if doesn't matter */ +void pq_set_compare(prio_queue_t, int (*compare)(void *a, void *b)); + +/* set the data free function (optional) + * (*free) will be called when: + * - dropping elements from the end of a limited size queue and + * - free_prio_queue() finds leftover elements in the given queue */ +void pq_set_free(prio_queue_t, void (*free)(void *)); + +/* set a maximum queue size (optional) (defaults to INT_MAX) */ +void pq_set_max_size(prio_queue_t, int); + +/* insert an element into the given queue */ +void insert_prio_elem(prio_queue_t, void *); + +/* return the number of elements in the queue */ +int pq_get_cur_size(prio_queue_t queue); + +/* pop the top-most element from the queue + * returns NULL if queue is empty */ +void *pop_prio_elem(prio_queue_t); + +/* clear and free the given queue */ +void free_prio_queue(prio_queue_t); + +#endif /* _PRIOQUEUE_H */ diff --git a/src/top.c b/src/top.c index 5394fed7..5147a59e 100644 --- a/src/top.c +++ b/src/top.c @@ -29,6 +29,7 @@ * */ +#include "prioqueue.h" #include "top.h" #include "logging.h" @@ -595,25 +596,11 @@ static void calc_io_each(void) * Find the top processes * ******************************************/ -/* free a sp_process structure */ -static void free_sp(struct sorted_process *sp) +/* cpu comparison function for prio queue */ +static int compare_cpu(void *va, void *vb) { - free(sp); -} + struct process *a = va, *b = vb; -/* create a new sp_process structure */ -static struct sorted_process *malloc_sp(struct process *proc) -{ - struct sorted_process *sp; - sp = malloc(sizeof(struct sorted_process)); - memset(sp, 0, sizeof(struct sorted_process)); - sp->proc = proc; - return sp; -} - -/* cpu comparison function for insert_sp_element */ -static int compare_cpu(struct process *a, struct process *b) -{ if (a->amount < b->amount) { return 1; } else if (a->amount > b->amount) { @@ -623,9 +610,11 @@ static int compare_cpu(struct process *a, struct process *b) } } -/* mem comparison function for insert_sp_element */ -static int compare_mem(struct process *a, struct process *b) +/* mem comparison function for prio queue */ +static int compare_mem(void *va, void *vb) { + struct process *a = va, *b = vb; + if (a->rss < b->rss) { return 1; } else if (a->rss > b->rss) { @@ -635,16 +624,20 @@ static int compare_mem(struct process *a, struct process *b) } } -/* CPU time comparision function for insert_sp_element */ -static int compare_time(struct process *a, struct process *b) +/* CPU time comparision function for prio queue */ +static int compare_time(void *va, void *vb) { + struct process *a = va, *b = vb; + return b->total_cpu_time - a->total_cpu_time; } #ifdef IOSTATS -/* I/O comparision function for insert_sp_element */ -static int compare_io(struct process *a, struct process *b) +/* I/O comparision function for prio queue */ +static int compare_io(void *va, void *vb) { + struct process *a = va, *b = vb; + if (a->io_perc < b->io_perc) { return 1; } else if (a->io_perc > b->io_perc) { @@ -655,78 +648,6 @@ static int compare_io(struct process *a, struct process *b) } #endif /* IOSTATS */ -/* insert this process into the list in a sorted fashion, - * or destroy it if it doesn't fit on the list */ -static int insert_sp_element(struct sorted_process *sp_cur, - struct sorted_process **p_sp_head, struct sorted_process **p_sp_tail, - int max_elements, int compare_funct(struct process *, struct process *)) -{ - - struct sorted_process *sp_readthru = NULL, *sp_destroy = NULL; - int did_insert = 0, x = 0; - - if (*p_sp_head == NULL) { - *p_sp_head = sp_cur; - *p_sp_tail = sp_cur; - return 1; - } - for (sp_readthru = *p_sp_head, x = 0; - sp_readthru != NULL && x < max_elements; - sp_readthru = sp_readthru->less, x++) { - if (compare_funct(sp_readthru->proc, sp_cur->proc) > 0 && !did_insert) { - /* sp_cur is bigger than sp_readthru - * so insert it before sp_readthru */ - sp_cur->less = sp_readthru; - if (sp_readthru == *p_sp_head) { - /* insert as the new head of the list */ - *p_sp_head = sp_cur; - } else { - /* insert inside the list */ - sp_readthru->greater->less = sp_cur; - sp_cur->greater = sp_readthru->greater; - } - sp_readthru->greater = sp_cur; - /* element was inserted, so increase the counter */ - did_insert = ++x; - } - } - if (x < max_elements && sp_readthru == NULL && !did_insert) { - /* sp_cur is the smallest element and list isn't full, - * so insert at the end */ - (*p_sp_tail)->less = sp_cur; - sp_cur->greater = *p_sp_tail; - *p_sp_tail = sp_cur; - did_insert = x; - } else if (x >= max_elements) { - /* We inserted an element and now the list is too big by one. - * Destroy the smallest element */ - sp_destroy = *p_sp_tail; - *p_sp_tail = sp_destroy->greater; - (*p_sp_tail)->less = NULL; - free_sp(sp_destroy); - } - if (!did_insert) { - /* sp_cur wasn't added to the sorted list, so destroy it */ - free_sp(sp_cur); - } - return did_insert; -} - -/* copy the procs in the sorted list to the array, and destroy the list */ -static void sp_acopy(struct sorted_process *sp_head, struct process **ar, int max_size) -{ - struct sorted_process *sp_cur, *sp_tmp; - int x; - - sp_cur = sp_head; - for (x = 0; x < max_size && sp_cur != NULL; x++) { - ar[x] = sp_cur->proc; - sp_tmp = sp_cur; - sp_cur = sp_cur->less; - free_sp(sp_tmp); - } -} - /* ****************************************************************** * * Get a sorted list of the top cpu hogs and top mem hogs. * * Results are stored in the cpu,mem arrays in decreasing order[0-9]. * @@ -739,14 +660,14 @@ void process_find_top(struct process **cpu, struct process **mem, #endif /* IOSTATS */ ) { - struct sorted_process *spc_head = NULL, *spc_tail = NULL, *spc_cur = NULL; - struct sorted_process *spm_head = NULL, *spm_tail = NULL, *spm_cur = NULL; - struct sorted_process *spt_head = NULL, *spt_tail = NULL, *spt_cur = NULL; + prio_queue_t cpu_queue, mem_queue, time_queue #ifdef IOSTATS - struct sorted_process *spi_head = NULL, *spi_tail = NULL, *spi_cur = NULL; -#endif /* IOSTATS */ + , io_queue +#endif + ; struct process *cur_proc = NULL; unsigned long long total = 0; + int i; if (!top_cpu && !top_mem && !top_time #ifdef IOSTATS @@ -757,6 +678,24 @@ void process_find_top(struct process **cpu, struct process **mem, return; } + cpu_queue = init_prio_queue(); + pq_set_compare(cpu_queue, &compare_cpu); + pq_set_max_size(cpu_queue, MAX_SP); + + mem_queue = init_prio_queue(); + pq_set_compare(mem_queue, &compare_mem); + pq_set_max_size(mem_queue, MAX_SP); + + time_queue = init_prio_queue(); + pq_set_compare(time_queue, &compare_time); + pq_set_max_size(time_queue, MAX_SP); + +#ifdef IOSTATS + io_queue = init_prio_queue(); + pq_set_compare(io_queue, &compare_io); + pq_set_max_size(io_queue, MAX_SP); +#endif + total = calc_cpu_total(); /* calculate the total of the processor */ update_process_table(); /* update the table with process list */ calc_cpu_each(total); /* and then the percentage for each task */ @@ -769,35 +708,39 @@ void process_find_top(struct process **cpu, struct process **mem, while (cur_proc != NULL) { if (top_cpu) { - spc_cur = malloc_sp(cur_proc); - insert_sp_element(spc_cur, &spc_head, &spc_tail, MAX_SP, - &compare_cpu); + insert_prio_elem(cpu_queue, cur_proc); } if (top_mem) { - spm_cur = malloc_sp(cur_proc); - insert_sp_element(spm_cur, &spm_head, &spm_tail, MAX_SP, - &compare_mem); + insert_prio_elem(mem_queue, cur_proc); } if (top_time) { - spt_cur = malloc_sp(cur_proc); - insert_sp_element(spt_cur, &spt_head, &spt_tail, MAX_SP, - &compare_time); + insert_prio_elem(time_queue, cur_proc); } #ifdef IOSTATS if (top_io) { - spi_cur = malloc_sp(cur_proc); - insert_sp_element(spi_cur, &spi_head, &spi_tail, MAX_SP, - &compare_io); + insert_prio_elem(io_queue, cur_proc); } #endif /* IOSTATS */ cur_proc = cur_proc->next; } - if (top_cpu) sp_acopy(spc_head, cpu, MAX_SP); - if (top_mem) sp_acopy(spm_head, mem, MAX_SP); - if (top_time) sp_acopy(spt_head, ptime, MAX_SP); + for (i = 0; i < MAX_SP; i++) { + if (top_cpu) + cpu[i] = pop_prio_elem(cpu_queue); + if (top_mem) + mem[i] = pop_prio_elem(mem_queue); + if (top_time) + ptime[i] = pop_prio_elem(time_queue); #ifdef IOSTATS - if (top_io) sp_acopy(spi_head, io, MAX_SP); + if (top_io) + io[i] = pop_prio_elem(io_queue); +#endif /* IOSTATS */ + } + free_prio_queue(cpu_queue); + free_prio_queue(mem_queue); + free_prio_queue(time_queue); +#ifdef IOSTATS + free_prio_queue(io_queue); #endif /* IOSTATS */ }