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