aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Andersen <andersen@codepoet.org>2002-09-17 22:14:58 +0000
committerEric Andersen <andersen@codepoet.org>2002-09-17 22:14:58 +0000
commit420b208a450d55e34df1301e939b7864b93d1026 (patch)
tree64f3e9b328d178df9cb02889e1212ad3f31a62cb
parent793c3b4a7dc986aee7b551eae5e8f3bd1200da5a (diff)
downloadbusybox-w32-420b208a450d55e34df1301e939b7864b93d1026.tar.gz
busybox-w32-420b208a450d55e34df1301e939b7864b93d1026.tar.bz2
busybox-w32-420b208a450d55e34df1301e939b7864b93d1026.zip
Add a new top applet
-Erik
-rw-r--r--include/applets.h3
-rw-r--r--include/usage.h9
-rw-r--r--procps/Makefile.in1
-rw-r--r--procps/config.in1
-rw-r--r--procps/top.c408
5 files changed, 422 insertions, 0 deletions
diff --git a/include/applets.h b/include/applets.h
index de07cc960..9508c3a4b 100644
--- a/include/applets.h
+++ b/include/applets.h
@@ -473,6 +473,9 @@
473#ifdef CONFIG_TIME 473#ifdef CONFIG_TIME
474 APPLET(time, time_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER) 474 APPLET(time, time_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER)
475#endif 475#endif
476#ifdef CONFIG_TOP
477 APPLET(top, top_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER)
478#endif
476#ifdef CONFIG_TOUCH 479#ifdef CONFIG_TOUCH
477 APPLET(touch, touch_main, _BB_DIR_BIN, _BB_SUID_NEVER) 480 APPLET(touch, touch_main, _BB_DIR_BIN, _BB_SUID_NEVER)
478#endif 481#endif
diff --git a/include/usage.h b/include/usage.h
index 1cdaa6681..33a81c467 100644
--- a/include/usage.h
+++ b/include/usage.h
@@ -1860,6 +1860,15 @@
1860 "Options:\n" \ 1860 "Options:\n" \
1861 "\t-v\tDisplays verbose resource usage information." 1861 "\t-v\tDisplays verbose resource usage information."
1862 1862
1863#define top_trivial_usage \
1864 "[-d <seconds>]"
1865#define top_full_usage \
1866 "top provides an view of processor activity in real time.\n" \
1867 "This utility reads the status for all processes in /proc each <seconds>\n" \
1868 "and shows the status for however many processes will fit on the screen.\n" \
1869 "This utility will not show processes that are started after program startup,\n" \
1870 "but it will show the EXIT status for and PIDs that exit while it is running."
1871
1863#define touch_trivial_usage \ 1872#define touch_trivial_usage \
1864 "[-c] FILE [FILE ...]" 1873 "[-c] FILE [FILE ...]"
1865#define touch_full_usage \ 1874#define touch_full_usage \
diff --git a/procps/Makefile.in b/procps/Makefile.in
index 1851d8953..150d2c31d 100644
--- a/procps/Makefile.in
+++ b/procps/Makefile.in
@@ -28,6 +28,7 @@ PROCPS-$(CONFIG_KILL) += kill.o
28PROCPS-$(CONFIG_PIDOF) += pidof.o 28PROCPS-$(CONFIG_PIDOF) += pidof.o
29PROCPS-$(CONFIG_PS) += ps.o 29PROCPS-$(CONFIG_PS) += ps.o
30PROCPS-$(CONFIG_RENICE) += renice.o 30PROCPS-$(CONFIG_RENICE) += renice.o
31PROCPS-$(CONFIG_TOP) += top.o
31PROCPS-$(CONFIG_UPTIME) += uptime.o 32PROCPS-$(CONFIG_UPTIME) += uptime.o
32 33
33libraries-y+=$(PROCPS_DIR)$(PROCPS_AR) 34libraries-y+=$(PROCPS_DIR)$(PROCPS_AR)
diff --git a/procps/config.in b/procps/config.in
index 94d76b606..be2dd090e 100644
--- a/procps/config.in
+++ b/procps/config.in
@@ -18,6 +18,7 @@ if [ "$CONFIG_PS" = "y" ] ; then
18 bool ' Use devps instead of /proc (needs a patched kernel)' CONFIG_FEATURE_USE_DEVPS_PATCH 18 bool ' Use devps instead of /proc (needs a patched kernel)' CONFIG_FEATURE_USE_DEVPS_PATCH
19fi 19fi
20bool 'renice' CONFIG_RENICE 20bool 'renice' CONFIG_RENICE
21bool 'top' CONFIG_TOP
21bool 'uptime' CONFIG_UPTIME 22bool 'uptime' CONFIG_UPTIME
22endmenu 23endmenu
23 24
diff --git a/procps/top.c b/procps/top.c
new file mode 100644
index 000000000..38211b345
--- /dev/null
+++ b/procps/top.c
@@ -0,0 +1,408 @@
1/*
2 * A tiny 'top' utility.
3 *
4 * This is written specifically for the linux 2.4 /proc/<PID>/status
5 * file format, but it checks that the file actually conforms to the
6 * format that this utility expects.
7
8 * This reads the PIDs of all processes at startup and then shows the
9 * status of those processes at given intervals. User can give
10 * maximum number of processes to show. If a process exits, it's PID
11 * is shown as 'EXIT'. If new processes are started while this works,
12 * it doesn't add them to the list of shown processes.
13 *
14 * NOTES:
15 * - At startup this changes to /proc, all the reads are then
16 * relative to that.
17 * - Includes code from the scandir() manual page.
18 *
19 * TODO:
20 * - ppid, uid etc could be read only once when program starts
21 * and rest of the information could be gotten from the
22 * /proc/<PID>/statm file.
23 * - Add process CPU and memory usage *percentages*.
24 *
25 * (C) Eero Tamminen <oak at welho dot com>
26 */
27#include <stdio.h>
28#include <stdlib.h>
29#include <unistd.h>
30#include <dirent.h>
31#include <string.h>
32#include <sys/ioctl.h>
33#include "busybox.h"
34
35
36/* process information taken from /proc,
37 * The code takes into account how long the fields below are,
38 * starting from copying the file from 'status' file to displaying it!
39 */
40typedef struct {
41 char uid[6]; /* User ID */
42 char pid[6]; /* Pid */
43 char ppid[6]; /* Parent Pid */
44 char name[12]; /* Name */
45 char cmd[20]; /* command line[read/show size] */
46 char state[2]; /* State: S, W... */
47 char size[9]; /* VmSize */
48 char lck[9]; /* VmLck */
49 char rss[9]; /* VmRSS */
50 char data[9]; /* VmData */
51 char stk[9]; /* VmStk */
52 char exe[9]; /* VmExe */
53 char lib[9]; /* VmLib */
54} status_t;
55
56/* display generic info (meminfo / loadavg) */
57static void display_generic(void)
58{
59 FILE *fp;
60 char buf[80];
61 float avg1, avg2, avg3;
62 unsigned long total, used, mfree, shared, buffers, cached;
63
64 /* read memory info */
65 fp = fopen("meminfo", "r");
66 if (!fp) {
67 perror("fopen('meminfo')");
68 return;
69 }
70 fgets(buf, sizeof(buf), fp); /* skip first line */
71
72 if (fscanf(fp, "Mem: %lu %lu %lu %lu %lu %lu",
73 &total, &used, &mfree, &shared, &buffers, &cached) != 6) {
74 fprintf(stderr, "Error: failed to read 'meminfo'");
75 fclose(fp);
76 }
77 fclose(fp);
78
79 /* read load average */
80 fp = fopen("loadavg", "r");
81 if (!fp) {
82 perror("fopen('loadavg')");
83 return;
84 }
85 if (fscanf(fp, "%f %f %f", &avg1, &avg2, &avg3) != 3) {
86 fprintf(stderr, "Error: failed to read 'loadavg'");
87 fclose(fp);
88 return;
89 }
90 fclose(fp);
91
92 /* convert to kilobytes */
93 if (total) total /= 1024;
94 if (used) used /= 1024;
95 if (mfree) mfree /= 1024;
96 if (shared) shared /= 1024;
97 if (buffers) buffers /= 1024;
98 if (cached) cached /= 1024;
99
100 /* output memory info and load average */
101 printf("Mem: %ldK, %ldK used, %ldK free, %ldK shrd, %ldK buff, %ldK cached\n",
102 total, used, mfree, shared, buffers, cached);
103 printf("Load average: %.2f, %.2f, %.2f (State: S=sleeping R=running, W=waiting)\n",
104 avg1, avg2, avg3);
105}
106
107
108/* display process statuses */
109static void display_status(int count, const status_t *s)
110{
111 const char *fmt, *cmd;
112
113 /* clear screen & go to top */
114 printf("\e[2J\e[1;1H");
115
116 display_generic();
117
118 /* what info of the processes is shown */
119 printf("\n%*s %*s %*s %*s %*s %*s %-*s\n",
120 sizeof(s->pid)-1, "Pid:",
121 sizeof(s->state)-1, "",
122 sizeof(s->ppid)-1, "PPid:",
123 sizeof(s->uid)-1, "UID:",
124 sizeof(s->size)-1, "WmSize:",
125 sizeof(s->rss)-1, "WmRSS:",
126 sizeof(s->cmd)-1, "command line:");
127
128 while (count--) {
129 if (s->cmd[0]) {
130 /* normal process, has command line */
131 cmd = s->cmd;
132 fmt = "%*s %*s %*s %*s %*s %*s %s\n";
133 } else {
134 /* no command line, show only process name */
135 cmd = s->name;
136 fmt = "%*s %*s %*s %*s %*s %*s [%s]\n";
137 }
138 printf(fmt,
139 sizeof(s->pid)-1, s->pid,
140 sizeof(s->state)-1, s->state,
141 sizeof(s->ppid)-1, s->ppid,
142 sizeof(s->uid)-1, s->uid,
143 sizeof(s->size)-1, s->size,
144 sizeof(s->rss)-1, s->rss,
145 cmd);
146 s++;
147 }
148}
149
150
151/* checks if given 'buf' for process starts with 'id' + ':' + TAB
152 * and stores rest of the buf to 'store' with max size 'size'
153 */
154static void process_status(const char *buf, const char *id, char *store, size_t size)
155{
156 int len, i;
157
158 if (!store) {
159 /* ignoring this field */
160 return;
161 }
162
163 /* check status field name */
164 len = strlen(id);
165 if (strncmp(buf, id, len) != 0) {
166 error_msg_and_die("ERROR status: line doesn't start with '%s' in:\n%s\n", id, buf);
167 }
168 buf += len;
169
170 /* check status field format */
171 if ((*buf++ != ':') || (*buf++ != '\t')) {
172 error_msg_and_die("ERROR status: field '%s' not followed with ':' + TAB in:\n%s\n", id, buf);
173 }
174
175 /* skip whitespace in Wm* fields */
176 if (id[0] == 'V' && id[1] == 'm') {
177 i = 3;
178 while (i--) {
179 if (*buf == ' ') {
180 buf++;
181 } else {
182 error_msg_and_die("ERROR status: can't skip whitespace for "
183 "'%s' field in:\n%s\n", id, buf);
184 }
185 }
186 }
187
188 /* copy at max (size-1) chars and force '\0' to the end */
189 while (--size) {
190 if (*buf < ' ') {
191 break;
192 }
193 *store++ = *buf++;
194 }
195 *store = '\0';
196}
197
198
199/* read process statuses */
200static void read_status(int num, status_t *s)
201{
202 char status[20];
203 char buf[80];
204 FILE *fp;
205
206 while (num--) {
207 sprintf(status, "%s/status", s->pid);
208
209 /* read the command line from 'cmdline' in PID dir */
210 fp = fopen(status, "r");
211 if (!fp) {
212 strncpy(s->pid, "EXIT", sizeof(s->pid));
213 continue;
214 }
215
216 /* get and process the information */
217 fgets(buf, sizeof(buf), fp);
218 process_status(buf, "Name", s->name, sizeof(s->name));
219 fgets(buf, sizeof(buf), fp);
220 process_status(buf, "State", s->state, sizeof(s->state));
221 fgets(buf, sizeof(buf), fp);
222 process_status(buf, "Tgid", NULL, 0);
223 fgets(buf, sizeof(buf), fp);
224 process_status(buf, "Pid", NULL, 0);
225 fgets(buf, sizeof(buf), fp);
226 process_status(buf, "PPid", s->ppid, sizeof(s->ppid));
227 fgets(buf, sizeof(buf), fp);
228 process_status(buf, "TracePid", NULL, 0);
229 fgets(buf, sizeof(buf), fp);
230 process_status(buf, "Uid", s->uid, sizeof(s->uid));
231 fgets(buf, sizeof(buf), fp);
232 process_status(buf, "Gid", NULL, 0);
233 fgets(buf, sizeof(buf), fp);
234 process_status(buf, "FDSize", NULL, 0);
235 fgets(buf, sizeof(buf), fp);
236 process_status(buf, "Groups", NULL, 0);
237 fgets(buf, sizeof(buf), fp);
238 /* only user space processes have command line
239 * and memory statistics
240 */
241 if (s->cmd[0]) {
242 process_status(buf, "VmSize", s->size, sizeof(s->size));
243 fgets(buf, sizeof(buf), fp);
244 process_status(buf, "VmLck", s->lck, sizeof(s->lck));
245 fgets(buf, sizeof(buf), fp);
246 process_status(buf, "VmRSS", s->rss, sizeof(s->rss));
247 fgets(buf, sizeof(buf), fp);
248 process_status(buf, "VmData", s->data, sizeof(s->data));
249 fgets(buf, sizeof(buf), fp);
250 process_status(buf, "VmStk", s->stk, sizeof(s->stk));
251 fgets(buf, sizeof(buf), fp);
252 process_status(buf, "VmExe", s->exe, sizeof(s->exe));
253 fgets(buf, sizeof(buf), fp);
254 process_status(buf, "VmLib", s->lib, sizeof(s->lib));
255 }
256 fclose(fp);
257
258 /* next process */
259 s++;
260 }
261}
262
263
264/* allocs statuslist and reads process command lines, frees namelist,
265 * returns filled statuslist or NULL in case of error.
266 */
267static status_t *read_info(int num, struct dirent **namelist)
268{
269 status_t *statuslist, *s;
270 char cmdline[20];
271 FILE *fp;
272 int idx;
273
274 /* allocate & zero status for each of the processes */
275 statuslist = calloc(num, sizeof(status_t));
276 if (!statuslist) {
277 return NULL;
278 }
279
280 /* go through the processes */
281 for (idx = 0; idx < num; idx++) {
282
283 /* copy PID string to status struct and free name */
284 s = &(statuslist[idx]);
285 if (strlen(namelist[idx]->d_name) > sizeof(s->pid)-1) {
286 fprintf(stderr, "PID '%s' too long\n", namelist[idx]->d_name);
287 return NULL;
288 }
289 strncpy(s->pid, namelist[idx]->d_name, sizeof(s->pid));
290 s->pid[sizeof(s->pid)-1] = '\0';
291 free(namelist[idx]);
292
293 /* read the command line from 'cmdline' in PID dir */
294 sprintf(cmdline, "%s/cmdline", s->pid);
295 fp = fopen(cmdline, "r");
296 if (!fp) {
297 perror("fopen('cmdline')");
298 return NULL;
299 }
300 fgets(statuslist[idx].cmd, sizeof(statuslist[idx].cmd), fp);
301 fclose(fp);
302 }
303 free(namelist);
304 return statuslist;
305}
306
307
308/* returns true for file names which are PID dirs
309 * (i.e. start with number)
310 */
311static int filter_pids(const struct dirent *dir)
312{
313 status_t dummy;
314 char *name = dir->d_name;
315
316 if (*name >= '0' && *name <= '9') {
317 if (strlen(name) > sizeof(dummy.pid)-1) {
318 fprintf(stderr, "PID name '%s' too long\n", name);
319 return 0;
320 }
321 return 1;
322 }
323 return 0;
324}
325
326
327/* compares two directory entry names as numeric strings
328 */
329static int num_sort(const void *a, const void *b)
330{
331 int ia = atoi((*(struct dirent **)a)->d_name);
332 int ib = atoi((*(struct dirent **)b)->d_name);
333
334 if (ia == ib) {
335 return 0;
336 }
337 /* NOTE: by switching the check, you change the process sort order */
338 if (ia < ib) {
339 return -1;
340 } else {
341 return 1;
342 }
343}
344
345
346int top_main(int argc, char **argv)
347{
348 status_t *statuslist;
349 struct dirent **namelist;
350 int opt, num, interval, lines;
351#if defined CONFIG_FEATURE_AUTOWIDTH && defined CONFIG_FEATURE_USE_TERMIOS
352 struct winsize win = { 0, 0, 0, 0 };
353#endif
354 /* Default update rate is 5 seconds */
355 interval = 5;
356 /* Default to 25 lines */
357 lines = 25;
358
359 /* do normal option parsing */
360 while ((opt = getopt(argc, argv, "d:")) > 0) {
361 switch (opt) {
362 case 'd':
363 interval = atoi(optarg);
364 break;
365 default:
366 show_usage();
367 }
368 }
369
370#if defined CONFIG_FEATURE_AUTOWIDTH && defined CONFIG_FEATURE_USE_TERMIOS
371 ioctl(fileno(stdout), TIOCGWINSZ, &win);
372 if (win.ws_row > 4)
373 lines = win.ws_row - 6;
374#endif
375
376 /* change to proc */
377 if (chdir("/proc") < 0) {
378 perror_msg_and_die("chdir('/proc')");
379 }
380
381 /* read process IDs for all the processes from the procfs */
382 num = scandir(".", &namelist, filter_pids, num_sort);
383 if (num < 0) {
384 perror_msg_and_die("scandir('/proc')");
385 }
386 if (lines > num) {
387 lines = num;
388 }
389
390 /* read command line for each of the processes */
391 statuslist = read_info(num, namelist);
392 if (!statuslist) {
393 return EXIT_FAILURE;
394 }
395
396 while (1) {
397 /* read status for each of the processes */
398 read_status(num, statuslist);
399
400 /* display status */
401 display_status(lines, statuslist);
402
403 sleep(interval);
404 }
405
406 free(statuslist);
407 return EXIT_SUCCESS;
408}