clarifying licensing
[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->totalmem = (float)(((float) process->rss / cur->memmax) / 10);
217         if (process->previous_user_time == ULONG_MAX)
218                 process->previous_user_time = process->user_time;
219         if (process->previous_kernel_time == ULONG_MAX)
220                 process->previous_kernel_time = process->kernel_time;
221
222         /* store the difference of the user_time */
223         user_time = process->user_time - process->previous_user_time;
224         kernel_time = process->kernel_time - process->previous_kernel_time;
225
226         /* backup the process->user_time for next time around */
227         process->previous_user_time = process->user_time;
228         process->previous_kernel_time = process->kernel_time;
229
230         /* store only the difference of the user_time here... */
231         process->user_time = user_time;
232         process->kernel_time = kernel_time;
233                   
234
235         return 0;
236 }
237
238 /******************************************/
239 /* Update process table                   */
240 /******************************************/
241
242 static int update_process_table()
243 {
244         DIR *dir;
245         struct dirent *entry;
246
247         if (!(dir = opendir("/proc")))
248                 return 1;
249
250         ++g_time;
251
252         /*
253          * Get list of processes from /proc directory
254          */
255         while ((entry = readdir(dir))) {
256                 pid_t pid;
257
258                 if (!entry) {
259                         /*
260                          * Problem reading list of processes
261                          */
262                         closedir(dir);
263                         return 1;
264                 }
265
266                 if (sscanf(entry->d_name, "%d", &pid) > 0) {
267                         struct process *p;
268                         p = find_process(pid);
269                         if (!p)
270                                 p = new_process(pid);
271
272                         /* compute each process cpu usage */
273                         calculate_cpu(p);
274                 }
275         }
276
277         closedir(dir);
278
279         return 0;
280 }
281
282 /******************************************/
283 /* Get process structure for process pid  */
284 /******************************************/
285
286 /*
287 * This function seems to hog all of the CPU time. I can't figure out why - it
288 * doesn't do much.
289 */
290 static int calculate_cpu(struct process *process)
291 {
292         int rc;
293
294         /* compute each process cpu usage by reading /proc/<proc#>/stat */
295         rc = process_parse_stat(process);
296         if (rc)
297                 return 1;
298         /*rc = process_parse_statm(process);
299            if (rc)
300            return 1; */
301
302         /*
303          * Check name against the exclusion list
304          */
305 /*      if (process->counted && exclusion_expression
306             && !regexec(exclusion_expression, process->name, 0, 0, 0))
307                 process->counted = 0;
308 */
309
310         return 0;
311 }
312
313 /******************************************/
314 /* Strip dead process entries             */
315 /******************************************/
316
317 static void process_cleanup()
318 {
319
320         struct process *p = first_process;
321         while (p) {
322                 struct process *current = p;
323
324 #if defined(PARANOID)
325                 assert(p->id == 0x0badfeed);
326 #endif                          /* defined(PARANOID) */
327
328                 p = p->next;
329                 /*
330                  * Delete processes that have died
331                  */
332                 if (current->time_stamp != g_time)
333                         delete_process(current);
334         }
335 }
336
337 /******************************************/
338 /* Destroy and remove a process           */
339 /******************************************/
340
341 static void delete_process(struct process *p)
342 {
343 #if defined(PARANOID)
344         assert(p->id == 0x0badfeed);
345
346         /*
347          * Ensure that deleted processes aren't reused.
348          */
349         p->id = 0x007babe;
350 #endif                          /* defined(PARANOID) */
351
352         /*
353          * Maintain doubly linked list.
354          */
355         if (p->next)
356                 p->next->previous = p->previous;
357         if (p->previous)
358                 p->previous->next = p->next;
359         else
360                 first_process = p->next;
361
362         if (p->name) {
363                 free(p->name);
364         }
365         free(p);
366 }
367
368 /******************************************/
369 /* Calculate cpu total                    */
370 /******************************************/
371 #define TMPL_SHORTPROC "%*s %llu %llu %llu %llu"
372 #define TMPL_LONGPROC "%*s %llu %llu %llu %llu %llu %llu %llu %llu"
373
374 static unsigned long long calc_cpu_total()
375 {
376         unsigned long long total = 0;
377         unsigned long long t = 0;
378         int rc;
379         int ps;
380         char line[BUFFER_LEN] = { 0 };
381         unsigned long long cpu = 0;
382         unsigned long long nice = 0;
383         unsigned long long system = 0;
384         unsigned long long idle = 0;
385         unsigned long long iowait = 0;
386         unsigned long long irq = 0;
387         unsigned long long softirq = 0;
388         unsigned long long steal = 0;
389         char * template = KFLAG_ISSET(KFLAG_IS_LONGSTAT) ? TMPL_LONGPROC : TMPL_SHORTPROC; 
390  
391         ps = open("/proc/stat", O_RDONLY);
392         rc = read(ps, line, sizeof(line));
393         close(ps);
394         if (rc < 0)
395                 return 0;
396
397         sscanf(line, template, &cpu, &nice, &system, &idle, &iowait, &irq, &softirq, &steal);
398         total = cpu + nice + system + idle + iowait + irq + softirq + steal;
399
400         t = total - previous_total;
401         previous_total = total;
402
403         return t;
404 }
405
406 /******************************************/
407 /* Calculate each processes cpu           */
408 /******************************************/
409
410 inline static void calc_cpu_each(unsigned long long total)
411 {
412         struct process *p = first_process;
413         while (p) {
414                 p->amount =
415                     100.0 * (p->user_time + p->kernel_time) / (float)total;
416
417                 p = p->next;
418         }
419 }
420
421 /******************************************/
422 /* Find the top processes                 */
423 /******************************************/
424
425 /*
426  * free a  sp_process structure
427 */
428 void free_sp(struct sorted_process * sp) {
429         free(sp);
430 }
431
432 /*
433  * create a new sp_process structure
434 */
435 struct sorted_process * malloc_sp(struct process * proc) {
436         struct sorted_process * sp;
437         sp = malloc(sizeof(struct sorted_process));
438         sp->greater = NULL;
439         sp->less = NULL;
440         sp->proc = proc;
441         return(sp);
442
443
444 /*
445  * cpu comparison function for insert_sp_element 
446  */
447 int compare_cpu(struct process *a, struct process *b) {
448         if (a->amount < b->amount) return 1; 
449         return 0;
450 }
451
452 /*
453  * mem comparison function for insert_sp_element 
454  */
455 int compare_mem(struct process *a, struct process *b) {
456         if (a->totalmem < b->totalmem) return 1; 
457         return 0;
458 }
459
460 /*
461  * insert this process into the list in a sorted fashion,
462  * or destroy it if it doesn't fit on the list
463 */ 
464 int insert_sp_element(
465                      struct sorted_process * sp_cur
466                    , struct sorted_process ** p_sp_head
467                    , struct sorted_process ** p_sp_tail
468                    , int max_elements
469                    , int (*compare_funct) (struct process *, struct process *)
470                   ) {
471
472         struct sorted_process * sp_readthru=NULL, * sp_destroy=NULL;
473         int did_insert = 0, x = 0;
474
475         if (*p_sp_head == NULL) {
476                 *p_sp_head = sp_cur;
477                 *p_sp_tail = sp_cur;
478                 return(1);
479         }
480         for(sp_readthru=*p_sp_head, x=0; sp_readthru != NULL && x < max_elements; sp_readthru=sp_readthru->less, x++) {
481                 if (compare_funct(sp_readthru->proc, sp_cur->proc) && !did_insert) {
482                         /* sp_cur is bigger than sp_readthru so insert it before sp_readthru */
483                         sp_cur->less=sp_readthru;
484                         if (sp_readthru == *p_sp_head) { 
485                                 *p_sp_head = sp_cur;  /* insert as the new head of the list */
486                         } else {
487                                 sp_readthru->greater->less = sp_cur;  /* insert inside  the list */
488                                 sp_cur->greater = sp_readthru->greater; 
489                         }
490                         sp_readthru->greater=sp_cur;
491                         did_insert = ++x;  /* element was inserted, so increase the counter */
492                 }
493         }
494         if (x < max_elements && sp_readthru == NULL && !did_insert) {
495                 /* sp_cur is the smallest element and list isn't full, so insert at the end */  
496                 (*p_sp_tail)->less=sp_cur;
497                 sp_cur->greater=*p_sp_tail;
498                 *p_sp_tail = sp_cur;
499                 did_insert=x;
500         } else if (x >= max_elements) {
501                 /* we inserted an element and now the list is too big by one. Destroy the smallest element */
502                 sp_destroy = *p_sp_tail;
503                 *p_sp_tail = sp_destroy->greater;
504                 (*p_sp_tail)->less = NULL;
505                 free_sp(sp_destroy);
506         }
507         if (!did_insert) {
508                 /* sp_cur wasn't added to the sorted list, so destroy it */
509                 free_sp(sp_cur);
510         }
511         return did_insert;
512 }
513   
514 /*
515  * copy the procs in the sorted list to the array, and destroy the list 
516  */
517 void sp_acopy(struct sorted_process *sp_head, struct process ** ar, int max_size)
518 {
519         struct sorted_process * sp_cur, * sp_tmp;
520         int x;
521         sp_cur = sp_head;
522         for (x=0; x < max_size && sp_cur != NULL; x++) {
523                 ar[x] = sp_cur->proc;   
524                 sp_tmp = sp_cur;
525                 sp_cur= sp_cur->less;
526                 free_sp(sp_tmp);        
527         }
528 }
529
530 // stole from common.c
531 #define NEED(a) ((need_mask & (1 << a)) && ((info.mask & (1 << a)) == 0))
532
533 /* ****************************************************************** */
534 /* Get a sorted list of the top cpu hogs and top mem hogs.            */
535 /* Results are stored in the cpu,mem arrays in decreasing order[0-9]. */
536 /* ****************************************************************** */
537
538 inline void process_find_top(struct process **cpu, struct process **mem)
539 {
540         struct sorted_process *spc_head = NULL, *spc_tail = NULL, *spc_cur = NULL;
541         struct sorted_process *spm_head = NULL, *spm_tail = NULL, *spm_cur = NULL;
542         struct process *cur_proc = NULL;
543         unsigned long long total = 0;
544
545         if (!top_cpu && !top_mem) return;
546
547         total = calc_cpu_total();       /* calculate the total of the processor */
548         update_process_table();         /* update the table with process list */
549         calc_cpu_each(total);           /* and then the percentage for each task */
550         process_cleanup();              /* cleanup list from exited processes */
551         
552         cur_proc = first_process;
553
554         while (cur_proc !=NULL) {
555                 if (top_cpu) {
556                         spc_cur = malloc_sp(cur_proc);
557                         insert_sp_element(spc_cur, &spc_head, &spc_tail, MAX_SP, &compare_cpu);
558                 }
559                 if (top_mem) {
560                         spm_cur = malloc_sp(cur_proc);
561                         insert_sp_element(spm_cur, &spm_head, &spm_tail, MAX_SP, &compare_mem);
562                 }
563                 cur_proc = cur_proc->next;
564         }
565         sp_acopy(spc_head, cpu, MAX_SP);
566         sp_acopy(spm_head, mem, MAX_SP);
567 }
568