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