respect CFLAGS/CPPFLAGS in env
[oweals/busybox.git] / procps / top.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * A tiny 'top' utility.
4  *
5  * This is written specifically for the linux /proc/<PID>/stat(m)
6  * files format.
7
8  * This reads the PIDs of all processes and their status and shows
9  * the status of processes (first ones that fit to screen) at given
10  * intervals.
11  *
12  * NOTES:
13  * - At startup this changes to /proc, all the reads are then
14  *   relative to that.
15  *
16  * (C) Eero Tamminen <oak at welho dot com>
17  *
18  * Rewritten by Vladimir Oleynik (C) 2002 <dzo@simtreas.ru>
19  */
20
21 /* Original code Copyrights */
22 /*
23  * Copyright (c) 1992 Branko Lankester
24  * Copyright (c) 1992 Roger Binns
25  * Copyright (C) 1994-1996 Charles L. Blake.
26  * Copyright (C) 1992-1998 Michael K. Johnson
27  * May be distributed under the conditions of the
28  * GNU Library General Public License
29  */
30
31 #include "busybox.h"
32
33
34 typedef struct {
35         unsigned long rss;
36 #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
37         unsigned long ticks;
38         unsigned pcpu; /* delta of ticks */
39 #endif
40         unsigned pid, ppid;
41         unsigned uid;
42         char state[4];
43         char comm[COMM_LEN];
44 } top_status_t;
45 static top_status_t *top;
46 static int ntop;
47 /* This structure stores some critical information from one frame to
48    the next. Used for finding deltas. */
49 struct save_hist {
50         unsigned long ticks;
51         unsigned pid;
52 };
53 #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
54 static struct save_hist *prev_hist;
55 static int prev_hist_count;
56 /* static int hist_iterations; */
57 static unsigned total_pcpu;
58 /* static unsigned long total_rss; */
59 #endif
60
61 #define OPT_BATCH_MODE (option_mask32 & 0x4)
62
63 #if ENABLE_FEATURE_USE_TERMIOS
64 static int pid_sort(top_status_t *P, top_status_t *Q)
65 {
66         /* Buggy wrt pids with high bit set */
67         /* (linux pids are in [1..2^15-1]) */
68         return (Q->pid - P->pid);
69 }
70 #endif
71
72 static int mem_sort(top_status_t *P, top_status_t *Q)
73 {
74         /* We want to avoid unsigned->signed and truncation errors */
75         if (Q->rss < P->rss) return -1;
76         return Q->rss != P->rss; /* 0 if ==, 1 if > */
77 }
78
79
80 typedef int (*cmp_funcp)(top_status_t *P, top_status_t *Q);
81
82 #if !ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
83
84 static cmp_funcp sort_function;
85
86 #else
87
88 enum { SORT_DEPTH = 3 };
89
90 static cmp_funcp sort_function[SORT_DEPTH];
91
92 static int pcpu_sort(top_status_t *P, top_status_t *Q)
93 {
94         /* Buggy wrt ticks with high bit set */
95         /* Affects only processes for which ticks overflow */
96         return (int)Q->pcpu - (int)P->pcpu;
97 }
98
99 static int time_sort(top_status_t *P, top_status_t *Q)
100 {
101         /* We want to avoid unsigned->signed and truncation errors */
102         if (Q->ticks < P->ticks) return -1;
103         return Q->ticks != P->ticks; /* 0 if ==, 1 if > */
104 }
105
106 static int mult_lvl_cmp(void* a, void* b) {
107         int i, cmp_val;
108
109         for (i = 0; i < SORT_DEPTH; i++) {
110                 cmp_val = (*sort_function[i])(a, b);
111                 if (cmp_val != 0)
112                         return cmp_val;
113         }
114         return 0;
115 }
116
117
118 typedef struct {
119         unsigned long long usr,nic,sys,idle,iowait,irq,softirq,steal;
120         unsigned long long total;
121         unsigned long long busy;
122 } jiffy_counts_t;
123 static jiffy_counts_t jif, prev_jif;
124 static void get_jiffy_counts(void)
125 {
126         FILE* fp = xfopen("stat", "r");
127         prev_jif = jif;
128         if (fscanf(fp, "cpu  %lld %lld %lld %lld %lld %lld %lld %lld",
129                         &jif.usr,&jif.nic,&jif.sys,&jif.idle,
130                         &jif.iowait,&jif.irq,&jif.softirq,&jif.steal) < 4) {
131                 bb_error_msg_and_die("failed to read /proc/stat");
132         }
133         fclose(fp);
134         jif.total = jif.usr + jif.nic + jif.sys + jif.idle
135                         + jif.iowait + jif.irq + jif.softirq + jif.steal;
136         /* procps 2.x does not count iowait as busy time */
137         jif.busy = jif.total - jif.idle - jif.iowait;
138 }
139
140
141 static void do_stats(void)
142 {
143         top_status_t *cur;
144         pid_t pid;
145         int i, last_i, n;
146         struct save_hist *new_hist;
147
148         get_jiffy_counts();
149         total_pcpu = 0;
150         /* total_rss = 0; */
151         new_hist = xmalloc(sizeof(struct save_hist)*ntop);
152         /*
153          * Make a pass through the data to get stats.
154          */
155         /* hist_iterations = 0; */
156         i = 0;
157         for (n = 0; n < ntop; n++) {
158                 cur = top + n;
159
160                 /*
161                  * Calculate time in cur process.  Time is sum of user time
162                  * and system time
163                  */
164                 pid = cur->pid;
165                 new_hist[n].ticks = cur->ticks;
166                 new_hist[n].pid = pid;
167
168                 /* find matching entry from previous pass */
169                 cur->pcpu = 0;
170                 /* do not start at index 0, continue at last used one
171                  * (brought hist_iterations from ~14000 down to 172) */
172                 last_i = i;
173                 if (prev_hist_count) do {
174                         if (prev_hist[i].pid == pid) {
175                                 cur->pcpu = cur->ticks - prev_hist[i].ticks;
176                                 total_pcpu += cur->pcpu;
177                                 break;
178                         }
179                         i = (i+1) % prev_hist_count;
180                         /* hist_iterations++; */
181                 } while (i != last_i);
182                 /* total_rss += cur->rss; */
183         }
184
185         /*
186          * Save cur frame's information.
187          */
188         free(prev_hist);
189         prev_hist = new_hist;
190         prev_hist_count = ntop;
191 }
192 #endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */
193
194
195 /* display generic info (meminfo / loadavg) */
196 static unsigned long display_generic(int scr_width)
197 {
198         FILE *fp;
199         char buf[80];
200         char scrbuf[80];
201         char *end;
202         unsigned long total, used, mfree, shared, buffers, cached;
203         unsigned int needs_conversion = 1;
204
205         /* read memory info */
206         fp = xfopen("meminfo", "r");
207
208         /*
209          * Old kernels (such as 2.4.x) had a nice summary of memory info that
210          * we could parse, however this is gone entirely in 2.6. Try parsing
211          * the old way first, and if that fails, parse each field manually.
212          *
213          * First, we read in the first line. Old kernels will have bogus
214          * strings we don't care about, whereas new kernels will start right
215          * out with MemTotal:
216          *                              -- PFM.
217          */
218         if (fscanf(fp, "MemTotal: %lu %s\n", &total, buf) != 2) {
219                 fgets(buf, sizeof(buf), fp);    /* skip first line */
220
221                 fscanf(fp, "Mem: %lu %lu %lu %lu %lu %lu",
222                    &total, &used, &mfree, &shared, &buffers, &cached);
223         } else {
224                 /*
225                  * Revert to manual parsing, which incidentally already has the
226                  * sizes in kilobytes. This should be safe for both 2.4 and
227                  * 2.6.
228                  */
229                 needs_conversion = 0;
230
231                 fscanf(fp, "MemFree: %lu %s\n", &mfree, buf);
232
233                 /*
234                  * MemShared: is no longer present in 2.6. Report this as 0,
235                  * to maintain consistent behavior with normal procps.
236                  */
237                 if (fscanf(fp, "MemShared: %lu %s\n", &shared, buf) != 2)
238                         shared = 0;
239
240                 fscanf(fp, "Buffers: %lu %s\n", &buffers, buf);
241                 fscanf(fp, "Cached: %lu %s\n", &cached, buf);
242
243                 used = total - mfree;
244         }
245         fclose(fp);
246
247         /* read load average as a string */
248         buf[0] = '\0';
249         open_read_close("loadavg", buf, sizeof(buf));
250         end = strchr(buf, ' ');
251         if (end) end = strchr(end+1, ' ');
252         if (end) end = strchr(end+1, ' ');
253         if (end) *end = '\0';
254
255         if (needs_conversion) {
256                 /* convert to kilobytes */
257                 used /= 1024;
258                 mfree /= 1024;
259                 shared /= 1024;
260                 buffers /= 1024;
261                 cached /= 1024;
262                 total /= 1024;
263         }
264
265         /* output memory info and load average */
266         /* clear screen & go to top */
267         if (scr_width > sizeof(scrbuf))
268                 scr_width = sizeof(scrbuf);
269         snprintf(scrbuf, scr_width,
270                 "Mem: %ldK used, %ldK free, %ldK shrd, %ldK buff, %ldK cached",
271                 used, mfree, shared, buffers, cached);
272
273         printf(OPT_BATCH_MODE ? "%s\n" : "\e[H\e[J%s\n", scrbuf);
274
275         snprintf(scrbuf, scr_width, "Load average: %s", buf);
276         printf("%s\n", scrbuf);
277
278         return total;
279 }
280
281
282 /* display process statuses */
283 static void display_status(int count, int scr_width)
284 {
285         enum {
286                 bits_per_int = sizeof(int)*8
287         };
288
289         top_status_t *s = top;
290         char rss_str_buf[8];
291         unsigned long total_memory = display_generic(scr_width); /* or use total_rss? */
292         unsigned pmem_shift, pmem_scale;
293
294 #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
295         unsigned pcpu_shift, pcpu_scale;
296         unsigned busy_jifs;
297
298         /* what info of the processes is shown */
299         printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width,
300                 "  PID USER     STATUS   RSS  PPID %CPU %MEM COMMAND");
301 #define MIN_WIDTH \
302         sizeof( "  PID USER     STATUS   RSS  PPID %CPU %MEM C")
303 #else
304         printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width,
305                 "  PID USER     STATUS   RSS  PPID %MEM COMMAND");
306 #define MIN_WIDTH \
307         sizeof( "  PID USER     STATUS   RSS  PPID %MEM C")
308 #endif
309
310         /*
311          * MEM% = s->rss/MemTotal
312          */
313         pmem_shift = bits_per_int-11;
314         pmem_scale = 1000*(1U<<(bits_per_int-11)) / total_memory;
315         /* s->rss is in kb. we want (s->rss * pmem_scale) to never overflow */
316         while (pmem_scale >= 512) {
317                 pmem_scale /= 4;
318                 pmem_shift -= 2;
319         }
320 #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
321         busy_jifs = jif.busy - prev_jif.busy;
322         /* This happens if there were lots of short-lived processes
323          * between two top updates (e.g. compilation) */
324         if (total_pcpu < busy_jifs) total_pcpu = busy_jifs;
325
326         /*
327          * CPU% = s->pcpu/sum(s->pcpu) * busy_cpu_ticks/total_cpu_ticks
328          * (pcpu is delta of sys+user time between samples)
329          */
330         /* (jif.xxx - prev_jif.xxx) and s->pcpu are
331          * in 0..~64000 range (HZ*update_interval).
332          * we assume that unsigned is at least 32-bit.
333          */
334         pcpu_shift = 6;
335         pcpu_scale = (1000*64*(uint16_t)busy_jifs ? : 1);
336         while (pcpu_scale < (1U<<(bits_per_int-2))) {
337                 pcpu_scale *= 4;
338                 pcpu_shift += 2;
339         }
340         pcpu_scale /= ( (uint16_t)(jif.total-prev_jif.total)*total_pcpu ? : 1);
341         /* we want (s->pcpu * pcpu_scale) to never overflow */
342         while (pcpu_scale >= 1024) {
343                 pcpu_scale /= 4;
344                 pcpu_shift -= 2;
345         }
346         /* printf(" pmem_scale=%u pcpu_scale=%u ", pmem_scale, pcpu_scale); */
347 #endif
348         while (count-- > 0) {
349                 div_t pmem = div((s->rss*pmem_scale) >> pmem_shift, 10);
350                 int col = scr_width+1;
351                 USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE(div_t pcpu;)
352
353                 if (s->rss >= 100*1024)
354                         sprintf(rss_str_buf, "%6ldM", s->rss/1024);
355                 else
356                         sprintf(rss_str_buf, "%7ld", s->rss);
357                 USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE(
358                 pcpu = div((s->pcpu*pcpu_scale) >> pcpu_shift, 10);
359                 )
360                 col -= printf("\n%5u %-8s %s  "
361                                 "%s%6u"
362                                 USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE("%3u.%c")
363                                 "%3u.%c ",
364                                 s->pid, get_cached_username(s->uid), s->state,
365                                 rss_str_buf, s->ppid,
366                                 USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE(pcpu.quot, '0'+pcpu.rem,)
367                                 pmem.quot, '0'+pmem.rem);
368                 if (col > 0)
369                         printf("%.*s", col, s->comm);
370                 /* printf(" %d/%d %lld/%lld", s->pcpu, total_pcpu,
371                         jif.busy - prev_jif.busy, jif.total - prev_jif.total); */
372                 s++;
373         }
374         /* printf(" %d", hist_iterations); */
375         putchar(OPT_BATCH_MODE ? '\n' : '\r');
376         fflush(stdout);
377 }
378
379
380 static void clearmems(void)
381 {
382         clear_username_cache();
383         free(top);
384         top = 0;
385         ntop = 0;
386 }
387
388
389 #if ENABLE_FEATURE_USE_TERMIOS
390 #include <termios.h>
391 #include <signal.h>
392
393 static struct termios initial_settings;
394
395 static void reset_term(void)
396 {
397         tcsetattr(0, TCSANOW, (void *) &initial_settings);
398 #if ENABLE_FEATURE_CLEAN_UP
399         clearmems();
400 #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
401         free(prev_hist);
402 #endif
403 #endif /* FEATURE_CLEAN_UP */
404 }
405
406 static void sig_catcher(int sig ATTRIBUTE_UNUSED)
407 {
408         reset_term();
409         exit(1);
410 }
411 #endif /* FEATURE_USE_TERMIOS */
412
413
414 int top_main(int argc, char **argv)
415 {
416         int count, lines, col;
417         unsigned interval = 5; /* default update rate is 5 seconds */
418         unsigned iterations = UINT_MAX; /* 2^32 iterations by default :) */
419         char *sinterval, *siterations;
420 #if ENABLE_FEATURE_USE_TERMIOS
421         struct termios new_settings;
422         struct timeval tv;
423         fd_set readfds;
424         unsigned char c;
425 #endif /* FEATURE_USE_TERMIOS */
426
427         /* do normal option parsing */
428         interval = 5;
429         opt_complementary = "-";
430         getopt32(argc, argv, "d:n:b", &sinterval, &siterations);
431         if (option_mask32 & 0x1) interval = xatou(sinterval); // -d
432         if (option_mask32 & 0x2) iterations = xatou(siterations); // -n
433         //if (option_mask32 & 0x4) // -b
434
435         /* change to /proc */
436         xchdir("/proc");
437 #if ENABLE_FEATURE_USE_TERMIOS
438         tcgetattr(0, (void *) &initial_settings);
439         memcpy(&new_settings, &initial_settings, sizeof(struct termios));
440         /* unbuffered input, turn off echo */
441         new_settings.c_lflag &= ~(ISIG | ICANON | ECHO | ECHONL);
442
443         signal(SIGTERM, sig_catcher);
444         signal(SIGINT, sig_catcher);
445         tcsetattr(0, TCSANOW, (void *) &new_settings);
446         atexit(reset_term);
447 #endif /* FEATURE_USE_TERMIOS */
448
449 #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
450         sort_function[0] = pcpu_sort;
451         sort_function[1] = mem_sort;
452         sort_function[2] = time_sort;
453 #else
454         sort_function = mem_sort;
455 #endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */
456
457         while (1) {
458                 procps_status_t *p = NULL;
459
460                 /* Default to 25 lines - 5 lines for status */
461                 lines = 24 - 3;
462                 col = 79;
463 #if ENABLE_FEATURE_USE_TERMIOS
464                 get_terminal_width_height(0, &col, &lines);
465                 if (lines < 5 || col < MIN_WIDTH) {
466                         sleep(interval);
467                         continue;
468                 }
469                 lines -= 3;
470 #endif /* FEATURE_USE_TERMIOS */
471
472                 /* read process IDs & status for all the processes */
473                 while ((p = procps_scan(p, 0
474                                 | PSSCAN_PID
475                                 | PSSCAN_PPID
476                                 | PSSCAN_RSS
477                                 | PSSCAN_STIME
478                                 | PSSCAN_UTIME
479                                 | PSSCAN_STATE
480                                 | PSSCAN_COMM
481                                 | PSSCAN_SID
482                                 | PSSCAN_UIDGID
483                 ))) {
484                         int n = ntop;
485                         top = xrealloc(top, (++ntop)*sizeof(top_status_t));
486                         top[n].pid = p->pid;
487                         top[n].ppid = p->ppid;
488                         top[n].rss = p->rss;
489 #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
490                         top[n].ticks = p->stime + p->utime;
491 #endif
492                         top[n].uid = p->uid;
493                         strcpy(top[n].state, p->state);
494                         strcpy(top[n].comm, p->comm);
495                 }
496                 if (ntop == 0) {
497                         bb_error_msg_and_die("can't find process info in /proc");
498                 }
499 #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
500                 if (!prev_hist_count) {
501                         do_stats();
502                         sleep(1);
503                         clearmems();
504                         continue;
505                 }
506                 do_stats();
507                 qsort(top, ntop, sizeof(top_status_t), (void*)mult_lvl_cmp);
508 #else
509                 qsort(top, ntop, sizeof(top_status_t), (void*)sort_function);
510 #endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */
511                 count = lines;
512                 if (OPT_BATCH_MODE || count > ntop) {
513                         count = ntop;
514                 }
515                 /* show status for each of the processes */
516                 display_status(count, col);
517 #if ENABLE_FEATURE_USE_TERMIOS
518                 tv.tv_sec = interval;
519                 tv.tv_usec = 0;
520                 FD_ZERO(&readfds);
521                 FD_SET(0, &readfds);
522                 select(1, &readfds, NULL, NULL, &tv);
523                 if (FD_ISSET(0, &readfds)) {
524                         if (read(0, &c, 1) <= 0) {   /* signal */
525                                 return EXIT_FAILURE;
526                         }
527                         if (c == 'q' || c == initial_settings.c_cc[VINTR])
528                                 break;
529                         if (c == 'M') {
530 #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
531                                 sort_function[0] = mem_sort;
532                                 sort_function[1] = pcpu_sort;
533                                 sort_function[2] = time_sort;
534 #else
535                                 sort_function = mem_sort;
536 #endif
537                         }
538 #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
539                         if (c == 'P') {
540                                 sort_function[0] = pcpu_sort;
541                                 sort_function[1] = mem_sort;
542                                 sort_function[2] = time_sort;
543                         }
544                         if (c == 'T') {
545                                 sort_function[0] = time_sort;
546                                 sort_function[1] = mem_sort;
547                                 sort_function[2] = pcpu_sort;
548                         }
549 #endif
550                         if (c == 'N') {
551 #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
552                                 sort_function[0] = pid_sort;
553 #else
554                                 sort_function = pid_sort;
555 #endif
556                         }
557                 }
558                 if (!--iterations)
559                         break;
560 #else
561                 sleep(interval);
562 #endif /* FEATURE_USE_TERMIOS */
563                 clearmems();
564         }
565         if (ENABLE_FEATURE_CLEAN_UP)
566                 clearmems();
567         putchar('\n');
568         return EXIT_SUCCESS;
569 }