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