diff options
author | Eric Andersen <andersen@codepoet.org> | 2002-09-17 22:14:58 +0000 |
---|---|---|
committer | Eric Andersen <andersen@codepoet.org> | 2002-09-17 22:14:58 +0000 |
commit | 420b208a450d55e34df1301e939b7864b93d1026 (patch) | |
tree | 64f3e9b328d178df9cb02889e1212ad3f31a62cb | |
parent | 793c3b4a7dc986aee7b551eae5e8f3bd1200da5a (diff) | |
download | busybox-w32-420b208a450d55e34df1301e939b7864b93d1026.tar.gz busybox-w32-420b208a450d55e34df1301e939b7864b93d1026.tar.bz2 busybox-w32-420b208a450d55e34df1301e939b7864b93d1026.zip |
Add a new top applet
-Erik
-rw-r--r-- | include/applets.h | 3 | ||||
-rw-r--r-- | include/usage.h | 9 | ||||
-rw-r--r-- | procps/Makefile.in | 1 | ||||
-rw-r--r-- | procps/config.in | 1 | ||||
-rw-r--r-- | procps/top.c | 408 |
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 | |||
28 | PROCPS-$(CONFIG_PIDOF) += pidof.o | 28 | PROCPS-$(CONFIG_PIDOF) += pidof.o |
29 | PROCPS-$(CONFIG_PS) += ps.o | 29 | PROCPS-$(CONFIG_PS) += ps.o |
30 | PROCPS-$(CONFIG_RENICE) += renice.o | 30 | PROCPS-$(CONFIG_RENICE) += renice.o |
31 | PROCPS-$(CONFIG_TOP) += top.o | ||
31 | PROCPS-$(CONFIG_UPTIME) += uptime.o | 32 | PROCPS-$(CONFIG_UPTIME) += uptime.o |
32 | 33 | ||
33 | libraries-y+=$(PROCPS_DIR)$(PROCPS_AR) | 34 | libraries-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 |
19 | fi | 19 | fi |
20 | bool 'renice' CONFIG_RENICE | 20 | bool 'renice' CONFIG_RENICE |
21 | bool 'top' CONFIG_TOP | ||
21 | bool 'uptime' CONFIG_UPTIME | 22 | bool 'uptime' CONFIG_UPTIME |
22 | endmenu | 23 | endmenu |
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 | */ | ||
40 | typedef 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) */ | ||
57 | static 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 */ | ||
109 | static 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 | */ | ||
154 | static 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 */ | ||
200 | static 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 | */ | ||
267 | static 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 | */ | ||
311 | static 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 | */ | ||
329 | static 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 | |||
346 | int 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 | } | ||