Use the internal process list for if_running on Linux
[monky] / src / top.c
1 /* Conky, a system monitor, based on torsmo
2  *
3  * Any original torsmo code is licensed under the BSD license
4  *
5  * All code written since the fork of torsmo is licensed under the GPL
6  *
7  * Please see COPYING for details
8  *
9  * Copyright (c) 2005 Adi Zaimi, Dan Piponi <dan@tanelorn.demon.co.uk>,
10  *                                        Dave Clark <clarkd@skynet.ca>
11  * Copyright (c) 2005-2009 Brenden Matthews, Philip Kovacs, et. al.
12  *      (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  */
28
29 #include "top.h"
30
31 static unsigned long g_time = 0;
32 static unsigned long long previous_total = 0;
33 static struct process *first_process = 0;
34
35 struct process *get_first_process(void)
36 {
37         return first_process;
38 }
39
40 void free_all_processes(void)
41 {
42         struct process *next = NULL, *pr = first_process;
43
44         while (pr) {
45                 next = pr->next;
46                 if (pr->name) {
47                         free(pr->name);
48                 }
49                 free(pr);
50                 pr = next;
51         }
52         first_process = NULL;
53 }
54
55 struct process *get_process_by_name(const char *name)
56 {
57         struct process *p = first_process;
58
59         while (p) {
60                 if (!strcmp(p->name, name))
61                         return p;
62                 p = p->next;
63         }
64         return 0;
65 }
66
67 static struct process *find_process(pid_t pid)
68 {
69         struct process *p = first_process;
70
71         while (p) {
72                 if (p->pid == pid) {
73                         return p;
74                 }
75                 p = p->next;
76         }
77         return 0;
78 }
79
80 /* Create a new process object and insert it into the process list */
81 static struct process *new_process(int p)
82 {
83         struct process *process;
84         process = (struct process *) malloc(sizeof(struct process));
85
86         // clean up memory first
87         memset(process, 0, sizeof(struct process));
88
89         /* Do stitching necessary for doubly linked list */
90         process->name = 0;
91         process->previous = 0;
92         process->next = first_process;
93         if (process->next) {
94                 process->next->previous = process;
95         }
96         first_process = process;
97
98         process->pid = p;
99         process->time_stamp = 0;
100         process->previous_user_time = ULONG_MAX;
101         process->previous_kernel_time = ULONG_MAX;
102 #ifdef IOSTATS
103         process->previous_read_bytes = ULLONG_MAX;
104         process->previous_write_bytes = ULLONG_MAX;
105 #endif
106         process->counted = 1;
107
108         /* process_find_name(process); */
109
110         return process;
111 }
112
113 /******************************************
114  * Functions                                                      *
115  ******************************************/
116
117 /******************************************
118  * Extract information from /proc                 *
119  ******************************************/
120
121 /* These are the guts that extract information out of /proc.
122  * Anyone hoping to port wmtop should look here first. */
123 static int process_parse_stat(struct process *process)
124 {
125         struct information *cur = &info;
126         char line[BUFFER_LEN] = { 0 }, filename[BUFFER_LEN], procname[BUFFER_LEN];
127         int ps;
128         unsigned long user_time = 0;
129         unsigned long kernel_time = 0;
130         int rc;
131         char *r, *q;
132         int endl;
133         int nice_val;
134         char *lparen, *rparen;
135
136         snprintf(filename, sizeof(filename), PROCFS_TEMPLATE, process->pid);
137
138         ps = open(filename, O_RDONLY);
139         if (ps < 0) {
140                 /* The process must have finished in the last few jiffies! */
141                 return 1;
142         }
143
144         /* Mark process as up-to-date. */
145         process->time_stamp = g_time;
146
147         rc = read(ps, line, sizeof(line));
148         close(ps);
149         if (rc < 0) {
150                 return 1;
151         }
152
153         /* Extract cpu times from data in /proc filesystem */
154         lparen = strchr(line, '(');
155         rparen = strrchr(line, ')');
156         if(!lparen || !rparen || rparen < lparen)
157                 return 1; // this should not happen
158
159         rc = MIN((unsigned)(rparen - lparen - 1), sizeof(procname) - 1);
160         strncpy(procname, lparen + 1, rc);
161         procname[rc] = '\0';
162         rc = sscanf(rparen + 1, "%*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %lu "
163                 "%lu %*s %*s %*s %d %*s %*s %*s %u %u", &process->user_time,
164                 &process->kernel_time, &nice_val, &process->vsize, &process->rss);
165         if (rc < 5) {
166                 return 1;
167         }
168         /* remove any "kdeinit: " */
169         if (procname == strstr(procname, "kdeinit")) {
170                 snprintf(filename, sizeof(filename), PROCFS_CMDLINE_TEMPLATE,
171                         process->pid);
172
173                 ps = open(filename, O_RDONLY);
174                 if (ps < 0) {
175                         /* The process must have finished in the last few jiffies! */
176                         return 1;
177                 }
178
179                 endl = read(ps, line, sizeof(line));
180                 close(ps);
181
182                 /* null terminate the input */
183                 line[endl] = 0;
184                 /* account for "kdeinit: " */
185                 if ((char *) line == strstr(line, "kdeinit: ")) {
186                         r = ((char *) line) + 9;
187                 } else {
188                         r = (char *) line;
189                 }
190
191                 q = procname;
192                 /* stop at space */
193                 while (*r && *r != ' ') {
194                         *q++ = *r++;
195                 }
196                 *q = 0;
197         }
198
199         if (process->name) {
200                 free(process->name);
201         }
202         process->name = strndup(procname, text_buffer_size);
203         process->rss *= getpagesize();
204
205         if (!cur->memmax) {
206                 update_total_processes();
207         }
208
209         process->total_cpu_time = process->user_time + process->kernel_time;
210         process->totalmem = (float) (((float) process->rss / cur->memmax) / 10);
211         if (process->previous_user_time == ULONG_MAX) {
212                 process->previous_user_time = process->user_time;
213         }
214         if (process->previous_kernel_time == ULONG_MAX) {
215                 process->previous_kernel_time = process->kernel_time;
216         }
217
218         /* store the difference of the user_time */
219         user_time = process->user_time - process->previous_user_time;
220         kernel_time = process->kernel_time - process->previous_kernel_time;
221
222         /* backup the process->user_time for next time around */
223         process->previous_user_time = process->user_time;
224         process->previous_kernel_time = process->kernel_time;
225
226         /* store only the difference of the user_time here... */
227         process->user_time = user_time;
228         process->kernel_time = kernel_time;
229
230         return 0;
231 }
232
233 #ifdef IOSTATS
234 static int process_parse_io(struct process *process)
235 {
236         static const char *read_bytes_str="read_bytes:";
237         static const char *write_bytes_str="write_bytes:";
238
239         char line[BUFFER_LEN] = { 0 }, filename[BUFFER_LEN];
240         int ps;
241         int rc;
242         char *pos, *endpos;
243         unsigned long long read_bytes, write_bytes;
244
245         snprintf(filename, sizeof(filename), PROCFS_TEMPLATE_IO, process->pid);
246
247         ps = open(filename, O_RDONLY);
248         if (ps < 0) {
249                 /* The process must have finished in the last few jiffies!
250                  * Or, the kernel doesn't support I/O accounting.
251                  */
252                 return 1;
253         }
254
255         rc = read(ps, line, sizeof(line));
256         close(ps);
257         if (rc < 0) {
258                 return 1;
259         }
260
261         pos = strstr(line, read_bytes_str);
262         if (pos == NULL) {
263                 /* these should not happen (unless the format of the file changes) */
264                 return 1;
265         }
266         pos += strlen(read_bytes_str);
267         process->read_bytes = strtoull(pos, &endpos, 10);
268         if (endpos == pos) {
269                 return 1;
270         }
271
272         pos = strstr(line, write_bytes_str);
273         if (pos == NULL) {
274                 return 1;
275         }
276         pos += strlen(write_bytes_str);
277         process->write_bytes = strtoull(pos, &endpos, 10);
278         if (endpos == pos) {
279                 return 1;
280         }
281
282         if (process->previous_read_bytes == ULLONG_MAX) {
283                 process->previous_read_bytes = process->read_bytes;
284         }
285         if (process->previous_write_bytes == ULLONG_MAX) {
286                 process->previous_write_bytes = process->write_bytes;
287         }
288
289         /* store the difference of the byte counts */
290         read_bytes = process->read_bytes - process->previous_read_bytes;
291         write_bytes = process->write_bytes - process->previous_write_bytes;
292
293         /* backup the counts for next time around */
294         process->previous_read_bytes = process->read_bytes;
295         process->previous_write_bytes = process->write_bytes;
296
297         /* store only the difference here... */
298         process->read_bytes = read_bytes;
299         process->write_bytes = write_bytes;
300
301         return 0;
302 }
303 #endif
304
305 /******************************************
306  * Get process structure for process pid  *
307  ******************************************/
308
309 /* This function seems to hog all of the CPU time.
310  * I can't figure out why - it doesn't do much. */
311 static int calculate_stats(struct process *process)
312 {
313         int rc;
314
315         /* compute each process cpu usage by reading /proc/<proc#>/stat */
316         rc = process_parse_stat(process);
317         if (rc)
318                 return 1;
319         /* rc = process_parse_statm(process); if (rc) return 1; */
320
321 #ifdef IOSTATS
322         rc = process_parse_io(process);
323         if (rc)
324                 return 1;
325 #endif
326
327         /*
328          * Check name against the exclusion list
329          */
330         /* if (process->counted && exclusion_expression &&
331          * !regexec(exclusion_expression, process->name, 0, 0, 0))
332          * process->counted = 0; */
333
334         return 0;
335 }
336
337 /******************************************
338  * Update process table                                   *
339  ******************************************/
340
341 static int update_process_table(void)
342 {
343         DIR *dir;
344         struct dirent *entry;
345
346         if (!(dir = opendir("/proc"))) {
347                 return 1;
348         }
349
350         ++g_time;
351
352         /* Get list of processes from /proc directory */
353         while ((entry = readdir(dir))) {
354                 pid_t pid;
355
356                 if (!entry) {
357                         /* Problem reading list of processes */
358                         closedir(dir);
359                         return 1;
360                 }
361
362                 if (sscanf(entry->d_name, "%d", &pid) > 0) {
363                         struct process *p;
364
365                         p = find_process(pid);
366                         if (!p) {
367                                 p = new_process(pid);
368                         }
369
370                         /* compute each process cpu usage */
371                         calculate_stats(p);
372                 }
373         }
374
375         closedir(dir);
376
377         return 0;
378 }
379
380 /******************************************
381  * Destroy and remove a process           *
382  ******************************************/
383
384 static void delete_process(struct process *p)
385 {
386 #if defined(PARANOID)
387         assert(p->id == 0x0badfeed);
388
389         /*
390          * Ensure that deleted processes aren't reused.
391          */
392         p->id = 0x007babe;
393 #endif /* defined(PARANOID) */
394
395         /*
396          * Maintain doubly linked list.
397          */
398         if (p->next)
399                 p->next->previous = p->previous;
400         if (p->previous)
401                 p->previous->next = p->next;
402         else
403                 first_process = p->next;
404
405         if (p->name) {
406                 free(p->name);
407         }
408         free(p);
409 }
410
411 /******************************************
412  * Strip dead process entries                     *
413  ******************************************/
414
415 static void process_cleanup(void)
416 {
417
418         struct process *p = first_process;
419
420         while (p) {
421                 struct process *current = p;
422
423 #if defined(PARANOID)
424                 assert(p->id == 0x0badfeed);
425 #endif /* defined(PARANOID) */
426
427                 p = p->next;
428                 /* Delete processes that have died */
429                 if (current->time_stamp != g_time) {
430                         delete_process(current);
431                 }
432         }
433 }
434
435 /******************************************
436  * Calculate cpu total                                    *
437  ******************************************/
438 #define TMPL_SHORTPROC "%*s %llu %llu %llu %llu"
439 #define TMPL_LONGPROC "%*s %llu %llu %llu %llu %llu %llu %llu %llu"
440
441 static unsigned long long calc_cpu_total(void)
442 {
443         unsigned long long total = 0;
444         unsigned long long t = 0;
445         int rc;
446         int ps;
447         char line[BUFFER_LEN] = { 0 };
448         unsigned long long cpu = 0;
449         unsigned long long niceval = 0;
450         unsigned long long systemval = 0;
451         unsigned long long idle = 0;
452         unsigned long long iowait = 0;
453         unsigned long long irq = 0;
454         unsigned long long softirq = 0;
455         unsigned long long steal = 0;
456         const char *template =
457                 KFLAG_ISSET(KFLAG_IS_LONGSTAT) ? TMPL_LONGPROC : TMPL_SHORTPROC;
458
459         ps = open("/proc/stat", O_RDONLY);
460         rc = read(ps, line, sizeof(line));
461         close(ps);
462         if (rc < 0) {
463                 return 0;
464         }
465
466         sscanf(line, template, &cpu, &niceval, &systemval, &idle, &iowait, &irq,
467                 &softirq, &steal);
468         total = cpu + niceval + systemval + idle + iowait + irq + softirq + steal;
469
470         t = total - previous_total;
471         previous_total = total;
472
473         return t;
474 }
475
476 /******************************************
477  * Calculate each processes cpu                   *
478  ******************************************/
479
480 inline static void calc_cpu_each(unsigned long long total)
481 {
482         struct process *p = first_process;
483
484         while (p) {
485                 p->amount = 100.0 * (cpu_separate ? info.cpu_count : 1) *
486                         (p->user_time + p->kernel_time) / (float) total;
487
488                 p = p->next;
489         }
490 }
491
492 #ifdef IOSTATS
493 static void calc_io_each(void)
494 {
495         struct process *p;
496         unsigned long long sum = 0;
497         
498         for (p = first_process; p; p = p->next)
499                 sum += p->read_bytes + p->write_bytes;
500
501         if(sum == 0)
502                 sum = 1; /* to avoid having NANs if no I/O occured */
503         for (p = first_process; p; p = p->next)
504                 p->io_perc = 100.0 * (p->read_bytes + p->write_bytes) / (float) sum;
505 }
506 #endif
507
508 /******************************************
509  * Find the top processes                                 *
510  ******************************************/
511
512 /* free a sp_process structure */
513 static void free_sp(struct sorted_process *sp)
514 {
515         free(sp);
516 }
517
518 /* create a new sp_process structure */
519 static struct sorted_process *malloc_sp(struct process *proc)
520 {
521         struct sorted_process *sp;
522         sp = malloc(sizeof(struct sorted_process));
523         sp->greater = NULL;
524         sp->less = NULL;
525         sp->proc = proc;
526         return sp;
527 }
528
529 /* cpu comparison function for insert_sp_element */
530 static int compare_cpu(struct process *a, struct process *b)
531 {
532         if (a->amount < b->amount) {
533                 return 1;
534         } else if (a->amount > b->amount) {
535                 return -1;
536         } else {
537                 return 0;
538         }
539 }
540
541 /* mem comparison function for insert_sp_element */
542 static int compare_mem(struct process *a, struct process *b)
543 {
544         if (a->totalmem < b->totalmem) {
545                 return 1;
546         } else if (a->totalmem > b->totalmem) {
547                 return -1;
548         } else {
549                 return 0;
550         }
551 }
552
553 /* CPU time comparision function for insert_sp_element */
554 static int compare_time(struct process *a, struct process *b)
555 {
556         return b->total_cpu_time - a->total_cpu_time;
557 }
558
559 #ifdef IOSTATS
560 /* I/O comparision function for insert_sp_element */
561 static int compare_io(struct process *a, struct process *b)
562 {
563         if (a->io_perc < b->io_perc) {
564                 return 1;
565         } else if (a->io_perc > b->io_perc) {
566                 return -1;
567         } else {
568                 return 0;
569         }
570 }
571 #endif
572
573 /* insert this process into the list in a sorted fashion,
574  * or destroy it if it doesn't fit on the list */
575 static int insert_sp_element(struct sorted_process *sp_cur,
576                 struct sorted_process **p_sp_head, struct sorted_process **p_sp_tail,
577                 int max_elements, int compare_funct(struct process *, struct process *))
578 {
579
580         struct sorted_process *sp_readthru = NULL, *sp_destroy = NULL;
581         int did_insert = 0, x = 0;
582
583         if (*p_sp_head == NULL) {
584                 *p_sp_head = sp_cur;
585                 *p_sp_tail = sp_cur;
586                 return 1;
587         }
588         for (sp_readthru = *p_sp_head, x = 0;
589                         sp_readthru != NULL && x < max_elements;
590                         sp_readthru = sp_readthru->less, x++) {
591                 if (compare_funct(sp_readthru->proc, sp_cur->proc) > 0 && !did_insert) {
592                         /* sp_cur is bigger than sp_readthru
593                          * so insert it before sp_readthru */
594                         sp_cur->less = sp_readthru;
595                         if (sp_readthru == *p_sp_head) {
596                                 /* insert as the new head of the list */
597                                 *p_sp_head = sp_cur;
598                         } else {
599                                 /* insert inside the list */
600                                 sp_readthru->greater->less = sp_cur;
601                                 sp_cur->greater = sp_readthru->greater;
602                         }
603                         sp_readthru->greater = sp_cur;
604                         /* element was inserted, so increase the counter */
605                         did_insert = ++x;
606                 }
607         }
608         if (x < max_elements && sp_readthru == NULL && !did_insert) {
609                 /* sp_cur is the smallest element and list isn't full,
610                  * so insert at the end */
611                 (*p_sp_tail)->less = sp_cur;
612                 sp_cur->greater = *p_sp_tail;
613                 *p_sp_tail = sp_cur;
614                 did_insert = x;
615         } else if (x >= max_elements) {
616                 /* We inserted an element and now the list is too big by one.
617                  * Destroy the smallest element */
618                 sp_destroy = *p_sp_tail;
619                 *p_sp_tail = sp_destroy->greater;
620                 (*p_sp_tail)->less = NULL;
621                 free_sp(sp_destroy);
622         }
623         if (!did_insert) {
624                 /* sp_cur wasn't added to the sorted list, so destroy it */
625                 free_sp(sp_cur);
626         }
627         return did_insert;
628 }
629
630 /* copy the procs in the sorted list to the array, and destroy the list */
631 static void sp_acopy(struct sorted_process *sp_head, struct process **ar, int max_size)
632 {
633         struct sorted_process *sp_cur, *sp_tmp;
634         int x;
635
636         sp_cur = sp_head;
637         for (x = 0; x < max_size && sp_cur != NULL; x++) {
638                 ar[x] = sp_cur->proc;
639                 sp_tmp = sp_cur;
640                 sp_cur = sp_cur->less;
641                 free_sp(sp_tmp);
642         }
643 }
644
645 /* ****************************************************************** *
646  * Get a sorted list of the top cpu hogs and top mem hogs.                        *
647  * Results are stored in the cpu,mem arrays in decreasing order[0-9]. *
648  * ****************************************************************** */
649
650 void process_find_top(struct process **cpu, struct process **mem,
651                 struct process **ptime
652 #ifdef IOSTATS
653                 , struct process **io
654 #endif
655                 )
656 {
657         struct sorted_process *spc_head = NULL, *spc_tail = NULL, *spc_cur = NULL;
658         struct sorted_process *spm_head = NULL, *spm_tail = NULL, *spm_cur = NULL;
659         struct sorted_process *spt_head = NULL, *spt_tail = NULL, *spt_cur = NULL;
660 #ifdef IOSTATS
661         struct sorted_process *spi_head = NULL, *spi_tail = NULL, *spi_cur = NULL;
662 #endif
663         struct process *cur_proc = NULL;
664         unsigned long long total = 0;
665
666         if (!top_cpu && !top_mem && !top_time
667 #ifdef IOSTATS
668                         && !top_io
669 #endif
670            ) {
671                 return;
672         }
673
674         total = calc_cpu_total();       /* calculate the total of the processor */
675         update_process_table();         /* update the table with process list */
676         calc_cpu_each(total);           /* and then the percentage for each task */
677         process_cleanup();                      /* cleanup list from exited processes */
678 #ifdef IOSTATS
679         calc_io_each();                 /* percentage of I/O for each task */
680 #endif
681
682         cur_proc = first_process;
683
684         while (cur_proc != NULL) {
685                 if (top_cpu) {
686                         spc_cur = malloc_sp(cur_proc);
687                         insert_sp_element(spc_cur, &spc_head, &spc_tail, MAX_SP,
688                                 &compare_cpu);
689                 }
690                 if (top_mem) {
691                         spm_cur = malloc_sp(cur_proc);
692                         insert_sp_element(spm_cur, &spm_head, &spm_tail, MAX_SP,
693                                 &compare_mem);
694                 }
695                 if (top_time) {
696                         spt_cur = malloc_sp(cur_proc);
697                         insert_sp_element(spt_cur, &spt_head, &spt_tail, MAX_SP,
698                                 &compare_time);
699                 }
700 #ifdef IOSTATS
701                 if (top_io) {
702                         spi_cur = malloc_sp(cur_proc);
703                         insert_sp_element(spi_cur, &spi_head, &spi_tail, MAX_SP,
704                                 &compare_io);
705                 }
706 #endif
707                 cur_proc = cur_proc->next;
708         }
709
710         if (top_cpu)
711                 sp_acopy(spc_head, cpu, MAX_SP);
712         if (top_mem)
713                 sp_acopy(spm_head, mem, MAX_SP);
714         if (top_time)
715                 sp_acopy(spt_head, ptime, MAX_SP);
716 #ifdef IOSTATS
717         if (top_io)
718                 sp_acopy(spi_head, io,MAX_SP);
719 #endif
720 }