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