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