new top routines using linked lists instead of arrays
[monky] / src / top.c
1 /*
2  * Conky, a system monitor, based on torsmo
3  *
4  * This program is licensed under BSD license, read COPYING
5  *
6  *  $Id$
7  */
8
9 #include "top.h"
10
11 static regex_t *exclusion_expression = 0;
12 static unsigned int g_time = 0;
13 static unsigned long previous_total = 0;
14 static struct process *first_process = 0;
15
16
17
18 static struct process *find_process(pid_t pid)
19 {
20         struct process *p = first_process;
21         while (p) {
22                 if (p->pid == pid)
23                         return p;
24                 p = p->next;
25         }
26         return 0;
27 }
28
29 /*
30 * Create a new process object and insert it into the process list
31 */
32 static struct process *new_process(int p)
33 {
34         struct process *process;
35         process = (struct process*)malloc(sizeof(struct process));
36
37         // clean up memory first
38         memset(process, 0, sizeof(struct process));
39
40         /*
41          * Do stitching necessary for doubly linked list
42          */
43         process->name = 0;
44         process->previous = 0;
45         process->next = first_process;
46         if (process->next)
47                 process->next->previous = process;
48         first_process = process;
49
50         process->pid = p;
51         process->time_stamp = 0;
52         process->previous_user_time = INT_MAX;
53         process->previous_kernel_time = INT_MAX;
54         process->counted = 1;
55
56         
57         /*    process_find_name(process); */
58
59         return process;
60 }
61
62 /******************************************/
63 /* Functions                              */
64 /******************************************/
65
66 static int process_parse_stat(struct process *);
67 static int update_process_table(void);
68 static int calculate_cpu(struct process *);
69 static void process_cleanup(void);
70 static void delete_process(struct process *);
71 /*inline void draw_processes(void);*/
72 static unsigned long calc_cpu_total(void);
73 static void calc_cpu_each(unsigned long);
74
75
76 /******************************************/
77 /* Extract information from /proc         */
78 /******************************************/
79
80 /*
81 * These are the guts that extract information out of /proc.
82 * Anyone hoping to port wmtop should look here first.
83 */
84 static int process_parse_stat(struct process *process)
85 {
86         struct information *cur;
87         cur = &info;
88         char line[BUFFER_LEN], filename[BUFFER_LEN], procname[BUFFER_LEN];
89         int ps;
90         unsigned long user_time = 0;
91         unsigned long kernel_time = 0;
92         int rc;
93         char *r, *q;
94         char deparenthesised_name[BUFFER_LEN];
95         int endl;
96         int nice_val;
97
98         snprintf(filename, sizeof(filename), PROCFS_TEMPLATE,
99                  process->pid);
100
101         ps = open(filename, O_RDONLY);
102         if (ps < 0)
103                 /*
104                  * The process must have finished in the last few jiffies!
105                  */
106                 return 1;
107
108         /*
109          * Mark process as up-to-date.
110          */
111         process->time_stamp = g_time;
112
113         rc = read(ps, line, sizeof(line));
114         close(ps);
115         if (rc < 0)
116                 return 1;
117
118         /*
119          * Extract cpu times from data in /proc filesystem
120          */
121         rc = sscanf(line,
122                     "%*s %s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %lu %lu %*s %*s %*s %d %*s %*s %*s %d %d",
123                     procname, &process->user_time, &process->kernel_time,
124                     &nice_val, &process->vsize, &process->rss);
125         if (rc < 5)
126                 return 1;
127         /*
128          * Remove parentheses from the process name stored in /proc/ under Linux...
129          */
130         r = procname + 1;
131         /* remove any "kdeinit: " */
132         if (r == strstr(r, "kdeinit")) {
133                 snprintf(filename, sizeof(filename),
134                          PROCFS_CMDLINE_TEMPLATE, process->pid);
135
136                 ps = open(filename, O_RDONLY);
137                 if (ps < 0)
138                         /*
139                          * The process must have finished in the last few jiffies!
140                          */
141                         return 1;
142
143                 endl = read(ps, line, sizeof(line));
144                 close(ps);
145
146                 /* null terminate the input */
147                 line[endl] = 0;
148                 /* account for "kdeinit: " */
149                 if ((char *) line == strstr(line, "kdeinit: "))
150                         r = ((char *) line) + 9;
151                 else
152                         r = (char *) line;
153
154                 q = deparenthesised_name;
155                 /* stop at space */
156                 while (*r && *r != ' ')
157                         *q++ = *r++;
158                 *q = 0;
159         } else {
160                 q = deparenthesised_name;
161                 while (*r && *r != ')')
162                         *q++ = *r++;
163                 *q = 0;
164         }
165
166         if (process->name)
167                 free(process->name);
168         process->name = strdup(deparenthesised_name);
169         process->rss *= getpagesize();
170
171         if (!cur->memmax)
172                 update_total_processes();
173
174
175
176         process->totalmem = (float)(((float) process->rss / cur->memmax) / 10);
177         if (process->previous_user_time == ULONG_MAX)
178                 process->previous_user_time = process->user_time;
179         if (process->previous_kernel_time == ULONG_MAX)
180                 process->previous_kernel_time = process->kernel_time;
181
182         /* store the difference of the user_time */
183         user_time = process->user_time - process->previous_user_time;
184         kernel_time = process->kernel_time - process->previous_kernel_time;
185
186         /* backup the process->user_time for next time around */
187         process->previous_user_time = process->user_time;
188         process->previous_kernel_time = process->kernel_time;
189
190         /* store only the difference of the user_time here... */
191         process->user_time = user_time;
192         process->kernel_time = kernel_time;
193
194
195         return 0;
196 }
197
198 /******************************************/
199 /* Update process table                   */
200 /******************************************/
201
202 static int update_process_table()
203 {
204         DIR *dir;
205         struct dirent *entry;
206
207         if (!(dir = opendir("/proc")))
208                 return 1;
209
210         ++g_time;
211
212         /*
213          * Get list of processes from /proc directory
214          */
215         while ((entry = readdir(dir))) {
216                 pid_t pid;
217
218                 if (!entry) {
219                         /*
220                          * Problem reading list of processes
221                          */
222                         closedir(dir);
223                         return 1;
224                 }
225
226                 if (sscanf(entry->d_name, "%d", &pid) > 0) {
227                         struct process *p;
228                         p = find_process(pid);
229                         if (!p)
230                                 p = new_process(pid);
231
232                         /* compute each process cpu usage */
233                         calculate_cpu(p);
234                 }
235         }
236
237         closedir(dir);
238
239         return 0;
240 }
241
242 /******************************************/
243 /* Get process structure for process pid  */
244 /******************************************/
245
246 /*
247 * This function seems to hog all of the CPU time. I can't figure out why - it
248 * doesn't do much.
249 */
250 static int calculate_cpu(struct process *process)
251 {
252         int rc;
253
254         /* compute each process cpu usage by reading /proc/<proc#>/stat */
255         rc = process_parse_stat(process);
256         if (rc)
257                 return 1;
258         /*rc = process_parse_statm(process);
259            if (rc)
260            return 1; */
261
262         /*
263          * Check name against the exclusion list
264          */
265         if (process->counted && exclusion_expression
266             && !regexec(exclusion_expression, process->name, 0, 0, 0))
267                 process->counted = 0;
268
269         return 0;
270 }
271
272 /******************************************/
273 /* Strip dead process entries             */
274 /******************************************/
275
276 static void process_cleanup()
277 {
278
279         struct process *p = first_process;
280         while (p) {
281                 struct process *current = p;
282
283 #if defined(PARANOID)
284                 assert(p->id == 0x0badfeed);
285 #endif                          /* defined(PARANOID) */
286
287                 p = p->next;
288                 /*
289                  * Delete processes that have died
290                  */
291                 if (current->time_stamp != g_time)
292                         delete_process(current);
293         }
294 }
295
296 /******************************************/
297 /* Destroy and remove a process           */
298 /******************************************/
299
300 static void delete_process(struct process *p)
301 {
302 #if defined(PARANOID)
303         assert(p->id == 0x0badfeed);
304
305         /*
306          * Ensure that deleted processes aren't reused.
307          */
308         p->id = 0x007babe;
309 #endif                          /* defined(PARANOID) */
310
311         /*
312          * Maintain doubly linked list.
313          */
314         if (p->next)
315                 p->next->previous = p->previous;
316         if (p->previous)
317                 p->previous->next = p->next;
318         else
319                 first_process = p->next;
320
321         if (p->name)
322                 free(p->name);
323         free(p);
324 }
325
326 /******************************************/
327 /* Calculate cpu total                    */
328 /******************************************/
329
330 static unsigned long calc_cpu_total()
331 {
332         unsigned long total = 0;
333         unsigned long t = 0;
334         int rc;
335         int ps;
336         char line[BUFFER_LEN];
337         unsigned long cpu = 0;
338         unsigned long nice = 0;
339         unsigned long system = 0;
340         unsigned long idle = 0;
341
342         ps = open("/proc/stat", O_RDONLY);
343         rc = read(ps, line, sizeof(line));
344         close(ps);
345         if (rc < 0)
346                 return 0;
347         sscanf(line, "%*s %lu %lu %lu %lu", &cpu, &nice, &system, &idle);
348         total = cpu + nice + system + idle;
349
350         t = total - previous_total;
351         previous_total = total;
352
353
354         return t;
355 }
356
357 /******************************************/
358 /* Calculate each processes cpu           */
359 /******************************************/
360
361 inline static void calc_cpu_each(unsigned long total)
362 {
363         struct process *p = first_process;
364         while (p) {
365                 /*p->amount = total ?
366                    (100.0 * (float) (p->user_time + p->kernel_time) /
367                    total) : 0; */
368                 p->amount =
369                     (100.0 * (p->user_time + p->kernel_time) / total);
370
371 /*              if (p->amount > 100)
372                 p->amount = 0;*/
373                 p = p->next;
374         }
375 }
376
377 /******************************************/
378 /* Find the top processes                 */
379 /******************************************/
380
381
382 /*
383  * cpu comparison function for insert_sp_element 
384  */
385 int compare_cpu(struct process *a, struct process *b) {
386         if (a->amount < b->amount) return 1; 
387         return 0;
388 }
389
390 /*
391  * mem comparison function for insert_sp_element 
392  */
393 int compare_mem(struct process *a, struct process *b) {
394         if (a->totalmem < b->totalmem) return 1; 
395         return 0;
396 }
397
398 /*
399  * insert this process into the list in a sorted fashion,
400  * or destroy it if it doesn't fit on the list
401 */ 
402 int insert_sp_element(  
403                      struct sorted_process * sp_cur
404                    , struct sorted_process ** p_sp_head
405                    , struct sorted_process ** p_sp_tail
406                    , int max_elements
407                    , int (*compare_funct) (struct process *, struct process *)
408                   ) {
409
410         struct sorted_process * sp_readthru=NULL, * sp_destroy=NULL;
411         int did_insert = 0, x = 0;
412
413         if (*p_sp_head == NULL) {
414                 *p_sp_head = sp_cur;
415                 *p_sp_tail = sp_cur;
416                 return(1);
417         }       
418         for(sp_readthru=*p_sp_head, x=0; sp_readthru != NULL && x < max_elements; sp_readthru=sp_readthru->less, x++) {
419                 if (compare_funct(sp_readthru->proc, sp_cur->proc) && !did_insert) {
420                         /* sp_cur is bigger than sp_readthru so insert it before sp_readthru */
421                         sp_cur->less=sp_readthru;
422                         if (sp_readthru == *p_sp_head) { 
423                                 *p_sp_head = sp_cur;  /* insert as the new head of the list */
424                         } else {
425                                 sp_readthru->greater->less = sp_cur;  /* insert inside  the list */
426                                 sp_cur->greater = sp_readthru->greater; 
427                         }
428                         sp_readthru->greater=sp_cur;
429                         did_insert = ++x;  /* element was inserted, so increase the counter */
430                 }
431         }  
432         if (x < max_elements && sp_readthru == NULL && !did_insert) {
433                 /* sp_cur is the smallest element and list isn't full, so insert at the end */  
434                 (*p_sp_tail)->less=sp_cur;
435                 sp_cur->greater=*p_sp_tail;
436                 *p_sp_tail = sp_cur;
437                 did_insert=x;
438         } else if (x==max_elements && sp_readthru != NULL) {
439                 /* we inserted an element and now the list is too big by one. Destroy the smallest element */
440                 sp_destroy = sp_readthru;
441                 sp_readthru->greater->less = NULL;
442                 *p_sp_tail = sp_readthru->greater;
443                 free(sp_destroy);
444         }
445         if (!did_insert) {
446                 /* sp_cur wasn't added to the sorted list, so destroy it */
447                 free(sp_cur);
448         }
449         return did_insert;
450 }               
451
452 /*
453  * create a new sp_process structure
454 */
455 struct sorted_process * malloc_sp(struct process * proc) {
456         struct sorted_process * sp;
457         sp = malloc(sizeof(struct sorted_process));
458         sp->greater = NULL;
459         sp->less = NULL;
460         sp->proc = proc;
461         return(sp);
462
463   
464 /*
465  * copy the procs in the sorted list to the array, and destroy the list 
466  */
467 void sp_acopy(struct sorted_process *sp_head, struct process ** ar, int max_size) {
468
469         struct sorted_process * sp_cur, * sp_tmp;
470         int x;
471
472         sp_cur = sp_head;
473         for (x=0; x < max_size && sp_cur != NULL; x++) {
474                 ar[x] = sp_cur->proc;   
475                 sp_tmp = sp_cur;
476                 sp_cur= sp_cur->less;
477                 free(sp_tmp);   
478         }
479 }
480
481 /* ****************************************************************** */
482 /* Get a sorted list of the top cpu hogs and top mem hogs.            */
483 /* Results are stored in the cpu,mem arrays in decreasing order[0-9]. */
484 /* ****************************************************************** */
485
486 inline void process_find_top(struct process **cpu, struct process **mem)
487 {
488         struct sorted_process *spc_head=NULL, *spc_tail=NULL, *spc_cur=NULL;
489         struct sorted_process *spm_head=NULL, *spm_tail=NULL, *spm_cur=NULL;
490         struct process *cur_proc=NULL;
491         unsigned long total =0;
492
493         if (!top_cpu && !top_mem) return;
494
495         total = calc_cpu_total();       /* calculate the total of the processor */
496         update_process_table();         /* update the table with process list */
497         calc_cpu_each(total);           /* and then the percentage for each task */
498         process_cleanup();              /* cleanup list from exited processes */
499         update_meminfo();
500         cur_proc = first_process;
501
502         while (cur_proc !=NULL) {
503                 if (top_cpu) { 
504                         spc_cur = malloc_sp(cur_proc);
505                         insert_sp_element(spc_cur, &spc_head, &spc_tail, MAX_SP, &compare_cpu); 
506                 } 
507                 if (top_mem) {
508                         spm_cur = malloc_sp(cur_proc);
509                         insert_sp_element(spm_cur, &spm_head, &spm_tail, MAX_SP, &compare_mem); 
510                 }
511                 cur_proc = cur_proc->next;
512         }
513         sp_acopy(spc_head, cpu, MAX_SP);
514         sp_acopy(spm_head, mem, MAX_SP);
515