diff options
Diffstat (limited to 'procps')
-rw-r--r-- | procps/Config.in | 14 | ||||
-rw-r--r-- | procps/Kbuild | 4 | ||||
-rw-r--r-- | procps/nmeter.c | 889 | ||||
-rw-r--r-- | procps/watch.c | 80 |
4 files changed, 986 insertions, 1 deletions
diff --git a/procps/Config.in b/procps/Config.in index 91319103f..b834fbf96 100644 --- a/procps/Config.in +++ b/procps/Config.in | |||
@@ -43,6 +43,12 @@ config KILLALL5 | |||
43 | default n | 43 | default n |
44 | depends on KILL | 44 | depends on KILL |
45 | 45 | ||
46 | config NMETER | ||
47 | bool "nmeter" | ||
48 | default n | ||
49 | help | ||
50 | Prints selected system stats continuously, one line per update. | ||
51 | |||
46 | config PIDOF | 52 | config PIDOF |
47 | bool "pidof" | 53 | bool "pidof" |
48 | default n | 54 | default n |
@@ -130,6 +136,14 @@ config UPTIME | |||
130 | the system has been running, how many users are currently logged | 136 | the system has been running, how many users are currently logged |
131 | on, and the system load averages for the past 1, 5, and 15 minutes. | 137 | on, and the system load averages for the past 1, 5, and 15 minutes. |
132 | 138 | ||
139 | config WATCH | ||
140 | bool "watch" | ||
141 | default n | ||
142 | #huh?? select DATE | ||
143 | help | ||
144 | watch is used to execute a program periodically, showing | ||
145 | output to the screen. | ||
146 | |||
133 | 147 | ||
134 | endmenu | 148 | endmenu |
135 | 149 | ||
diff --git a/procps/Kbuild b/procps/Kbuild index 33f616fc2..c75be291b 100644 --- a/procps/Kbuild +++ b/procps/Kbuild | |||
@@ -6,12 +6,14 @@ | |||
6 | 6 | ||
7 | lib-y:= | 7 | lib-y:= |
8 | lib-$(CONFIG_FREE) += free.o | 8 | lib-$(CONFIG_FREE) += free.o |
9 | lib-$(CONFIG_FUSER) += fuser.o | ||
9 | lib-$(CONFIG_KILL) += kill.o | 10 | lib-$(CONFIG_KILL) += kill.o |
10 | lib-$(CONFIG_ASH) += kill.o # used for built-in kill by ash | 11 | lib-$(CONFIG_ASH) += kill.o # used for built-in kill by ash |
12 | lib-$(CONFIG_NMETER) += nmeter.o | ||
11 | lib-$(CONFIG_PIDOF) += pidof.o | 13 | lib-$(CONFIG_PIDOF) += pidof.o |
12 | lib-$(CONFIG_PS) += ps.o | 14 | lib-$(CONFIG_PS) += ps.o |
13 | lib-$(CONFIG_RENICE) += renice.o | 15 | lib-$(CONFIG_RENICE) += renice.o |
14 | lib-$(CONFIG_BB_SYSCTL) += sysctl.o | 16 | lib-$(CONFIG_BB_SYSCTL) += sysctl.o |
15 | lib-$(CONFIG_TOP) += top.o | 17 | lib-$(CONFIG_TOP) += top.o |
16 | lib-$(CONFIG_UPTIME) += uptime.o | 18 | lib-$(CONFIG_UPTIME) += uptime.o |
17 | lib-$(CONFIG_FUSER) += fuser.o | 19 | lib-$(CONFIG_WATCH) += watch.o |
diff --git a/procps/nmeter.c b/procps/nmeter.c new file mode 100644 index 000000000..1d58eb2c1 --- /dev/null +++ b/procps/nmeter.c | |||
@@ -0,0 +1,889 @@ | |||
1 | /* | ||
2 | ** Licensed under the GPL v2, see the file LICENSE in this tarball | ||
3 | ** | ||
4 | ** Based on nanotop.c from floppyfw project | ||
5 | ** | ||
6 | ** Contact me: vda.linux@googlemail.com */ | ||
7 | |||
8 | //TODO: | ||
9 | // simplify code | ||
10 | // /proc/locks | ||
11 | // /proc/stat: | ||
12 | // disk_io: (3,0):(22272,17897,410702,4375,54750) | ||
13 | // btime 1059401962 | ||
14 | |||
15 | #include "libbb.h" | ||
16 | #include <time.h> | ||
17 | |||
18 | typedef unsigned long long ullong; | ||
19 | |||
20 | enum { PROC_FILE_SIZE = 4096 }; | ||
21 | |||
22 | typedef struct proc_file { | ||
23 | char *file; | ||
24 | //const char *name; | ||
25 | smallint last_gen; | ||
26 | } proc_file; | ||
27 | |||
28 | static const char *const proc_name[] = { | ||
29 | "stat", // Must match the order of proc_file's! | ||
30 | "loadavg", | ||
31 | "net/dev", | ||
32 | "meminfo", | ||
33 | "diskstats", | ||
34 | "sys/fs/file-nr", | ||
35 | }; | ||
36 | |||
37 | struct globals { | ||
38 | // Sample generation flip-flop | ||
39 | smallint gen; | ||
40 | // Linux 2.6? (otherwise assumes 2.4) | ||
41 | smallint is26; | ||
42 | // 1 if sample delay is not an integer fraction of a second | ||
43 | smallint need_seconds; | ||
44 | char *cur_outbuf; | ||
45 | const char *final_str; | ||
46 | int delta; | ||
47 | int deltanz; | ||
48 | struct timeval tv; | ||
49 | #define first_proc_file proc_stat | ||
50 | proc_file proc_stat; // Must match the order of proc_name's! | ||
51 | proc_file proc_loadavg; | ||
52 | proc_file proc_net_dev; | ||
53 | proc_file proc_meminfo; | ||
54 | proc_file proc_diskstats; | ||
55 | proc_file proc_sys_fs_filenr; | ||
56 | }; | ||
57 | #define G (*ptr_to_globals) | ||
58 | #define gen (G.gen ) | ||
59 | #define is26 (G.is26 ) | ||
60 | #define need_seconds (G.need_seconds ) | ||
61 | #define cur_outbuf (G.cur_outbuf ) | ||
62 | #define final_str (G.final_str ) | ||
63 | #define delta (G.delta ) | ||
64 | #define deltanz (G.deltanz ) | ||
65 | #define tv (G.tv ) | ||
66 | #define proc_stat (G.proc_stat ) | ||
67 | #define proc_loadavg (G.proc_loadavg ) | ||
68 | #define proc_net_dev (G.proc_net_dev ) | ||
69 | #define proc_meminfo (G.proc_meminfo ) | ||
70 | #define proc_diskstats (G.proc_diskstats ) | ||
71 | #define proc_sys_fs_filenr (G.proc_sys_fs_filenr) | ||
72 | #define INIT_G() do { \ | ||
73 | PTR_TO_GLOBALS = xzalloc(sizeof(G)); \ | ||
74 | cur_outbuf = outbuf; \ | ||
75 | final_str = "\n"; \ | ||
76 | deltanz = delta = 1000000; \ | ||
77 | } while (0) | ||
78 | |||
79 | // We depend on this being a char[], not char* - we take sizeof() of it | ||
80 | #define outbuf bb_common_bufsiz1 | ||
81 | |||
82 | static inline void reset_outbuf(void) | ||
83 | { | ||
84 | cur_outbuf = outbuf; | ||
85 | } | ||
86 | |||
87 | static inline int outbuf_count(void) | ||
88 | { | ||
89 | return cur_outbuf - outbuf; | ||
90 | } | ||
91 | |||
92 | static void print_outbuf(void) | ||
93 | { | ||
94 | int sz = cur_outbuf - outbuf; | ||
95 | if (sz > 0) { | ||
96 | xwrite(1, outbuf, sz); | ||
97 | cur_outbuf = outbuf; | ||
98 | } | ||
99 | } | ||
100 | |||
101 | static void put(const char *s) | ||
102 | { | ||
103 | int sz = strlen(s); | ||
104 | if (sz > outbuf + sizeof(outbuf) - cur_outbuf) | ||
105 | sz = outbuf + sizeof(outbuf) - cur_outbuf; | ||
106 | memcpy(cur_outbuf, s, sz); | ||
107 | cur_outbuf += sz; | ||
108 | } | ||
109 | |||
110 | static void put_c(char c) | ||
111 | { | ||
112 | if (cur_outbuf < outbuf + sizeof(outbuf)) | ||
113 | *cur_outbuf++ = c; | ||
114 | } | ||
115 | |||
116 | static void put_question_marks(int count) | ||
117 | { | ||
118 | while (count--) | ||
119 | put_c('?'); | ||
120 | } | ||
121 | |||
122 | static void readfile_z(char *buf, int sz, const char* fname) | ||
123 | { | ||
124 | // open_read_close() will do two reads in order to be sure we are at EOF, | ||
125 | // and we don't need/want that. | ||
126 | // sz = open_read_close(fname, buf, sz-1); | ||
127 | |||
128 | int fd = xopen(fname, O_RDONLY); | ||
129 | buf[0] = '\0'; | ||
130 | if (fd >= 0) { | ||
131 | sz = read(fd, buf, sz-1); | ||
132 | if (sz > 0) buf[sz] = '\0'; | ||
133 | close(fd); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | static const char* get_file(proc_file *pf) | ||
138 | { | ||
139 | if (pf->last_gen != gen) { | ||
140 | pf->last_gen = gen; | ||
141 | // We allocate PROC_FILE_SIZE bytes. This wastes memory, | ||
142 | // but allows us to allocate only once (at first sample) | ||
143 | // per proc file, and reuse buffer for each sample | ||
144 | if (!pf->file) | ||
145 | pf->file = xmalloc(PROC_FILE_SIZE); | ||
146 | readfile_z(pf->file, PROC_FILE_SIZE, proc_name[pf - &first_proc_file]); | ||
147 | } | ||
148 | return pf->file; | ||
149 | } | ||
150 | |||
151 | static inline ullong read_after_slash(const char *p) | ||
152 | { | ||
153 | p = strchr(p, '/'); | ||
154 | if (!p) return 0; | ||
155 | return strtoull(p+1, NULL, 10); | ||
156 | } | ||
157 | |||
158 | enum conv_type { conv_decimal, conv_slash }; | ||
159 | |||
160 | // Reads decimal values from line. Values start after key, for example: | ||
161 | // "cpu 649369 0 341297 4336769..." - key is "cpu" here. | ||
162 | // Values are stored in vec[]. arg_ptr has list of positions | ||
163 | // we are interested in: for example: 1,2,5 - we want 1st, 2nd and 5th value. | ||
164 | static int vrdval(const char* p, const char* key, | ||
165 | enum conv_type conv, ullong *vec, va_list arg_ptr) | ||
166 | { | ||
167 | int indexline; | ||
168 | int indexnext; | ||
169 | |||
170 | p = strstr(p, key); | ||
171 | if (!p) return 1; | ||
172 | |||
173 | p += strlen(key); | ||
174 | indexline = 1; | ||
175 | indexnext = va_arg(arg_ptr, int); | ||
176 | while (1) { | ||
177 | while (*p == ' ' || *p == '\t') p++; | ||
178 | if (*p == '\n' || *p == '\0') break; | ||
179 | |||
180 | if (indexline == indexnext) { // read this value | ||
181 | *vec++ = conv==conv_decimal ? | ||
182 | strtoull(p, NULL, 10) : | ||
183 | read_after_slash(p); | ||
184 | indexnext = va_arg(arg_ptr, int); | ||
185 | } | ||
186 | while (*p > ' ') p++; // skip over value | ||
187 | indexline++; | ||
188 | } | ||
189 | return 0; | ||
190 | } | ||
191 | |||
192 | // Parses files with lines like "cpu0 21727 0 15718 1813856 9461 10485 0 0": | ||
193 | // rdval(file_contents, "string_to_find", result_vector, value#, value#...) | ||
194 | // value# start with 1 | ||
195 | static int rdval(const char* p, const char* key, ullong *vec, ...) | ||
196 | { | ||
197 | va_list arg_ptr; | ||
198 | int result; | ||
199 | |||
200 | va_start(arg_ptr, vec); | ||
201 | result = vrdval(p, key, conv_decimal, vec, arg_ptr); | ||
202 | va_end(arg_ptr); | ||
203 | |||
204 | return result; | ||
205 | } | ||
206 | |||
207 | // Parses files with lines like "... ... ... 3/148 ...." | ||
208 | static int rdval_loadavg(const char* p, ullong *vec, ...) | ||
209 | { | ||
210 | va_list arg_ptr; | ||
211 | int result; | ||
212 | |||
213 | va_start(arg_ptr, vec); | ||
214 | result = vrdval(p, "", conv_slash, vec, arg_ptr); | ||
215 | va_end(arg_ptr); | ||
216 | |||
217 | return result; | ||
218 | } | ||
219 | |||
220 | // Parses /proc/diskstats | ||
221 | // 1 2 3 4 5 6(rd) 7 8 9 10(wr) 11 12 13 14 | ||
222 | // 3 0 hda 51292 14441 841783 926052 25717 79650 843256 3029804 0 148459 3956933 | ||
223 | // 3 1 hda1 0 0 0 0 <- ignore if only 4 fields | ||
224 | static int rdval_diskstats(const char* p, ullong *vec) | ||
225 | { | ||
226 | ullong rd = 0; // to avoid "warning: 'rd' might be used uninitialized" | ||
227 | int indexline = 0; | ||
228 | vec[0] = 0; | ||
229 | vec[1] = 0; | ||
230 | while (1) { | ||
231 | indexline++; | ||
232 | while (*p == ' ' || *p == '\t') p++; | ||
233 | if (*p == '\0') break; | ||
234 | if (*p == '\n') { | ||
235 | indexline = 0; | ||
236 | p++; | ||
237 | continue; | ||
238 | } | ||
239 | if (indexline == 6) { | ||
240 | rd = strtoull(p, NULL, 10); | ||
241 | } else if (indexline == 10) { | ||
242 | vec[0] += rd; // TODO: *sectorsize (don't know how to find out sectorsize) | ||
243 | vec[1] += strtoull(p, NULL, 10); | ||
244 | while (*p != '\n' && *p != '\0') p++; | ||
245 | continue; | ||
246 | } | ||
247 | while (*p > ' ') p++; // skip over value | ||
248 | } | ||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | static void scale(ullong ul) | ||
253 | { | ||
254 | char buf[5]; | ||
255 | smart_ulltoa5(ul, buf); | ||
256 | put(buf); | ||
257 | } | ||
258 | |||
259 | |||
260 | #define S_STAT(a) \ | ||
261 | typedef struct a { \ | ||
262 | struct s_stat *next; \ | ||
263 | void (*collect)(struct a *s); \ | ||
264 | const char *label; | ||
265 | #define S_STAT_END(a) } a; | ||
266 | |||
267 | S_STAT(s_stat) | ||
268 | S_STAT_END(s_stat) | ||
269 | |||
270 | static void collect_literal(s_stat *s) | ||
271 | { | ||
272 | } | ||
273 | |||
274 | static s_stat* init_literal(void) | ||
275 | { | ||
276 | s_stat *s = xmalloc(sizeof(s_stat)); | ||
277 | s->collect = collect_literal; | ||
278 | return (s_stat*)s; | ||
279 | } | ||
280 | |||
281 | static s_stat* init_delay(const char *param) | ||
282 | { | ||
283 | delta = bb_strtoi(param, NULL, 0) * 1000; | ||
284 | deltanz = delta > 0 ? delta : 1; | ||
285 | need_seconds = (1000000%deltanz) != 0; | ||
286 | return NULL; | ||
287 | } | ||
288 | |||
289 | static s_stat* init_cr(const char *param) | ||
290 | { | ||
291 | final_str = "\r"; | ||
292 | return (s_stat*)0; | ||
293 | } | ||
294 | |||
295 | |||
296 | // user nice system idle iowait irq softirq (last 3 only in 2.6) | ||
297 | //cpu 649369 0 341297 4336769 11640 7122 1183 | ||
298 | //cpuN 649369 0 341297 4336769 11640 7122 1183 | ||
299 | enum { CPU_FIELDCNT = 7 }; | ||
300 | S_STAT(cpu_stat) | ||
301 | ullong old[CPU_FIELDCNT]; | ||
302 | int bar_sz; | ||
303 | char *bar; | ||
304 | S_STAT_END(cpu_stat) | ||
305 | |||
306 | |||
307 | static void collect_cpu(cpu_stat *s) | ||
308 | { | ||
309 | ullong data[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 }; | ||
310 | unsigned frac[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 }; | ||
311 | ullong all = 0; | ||
312 | int norm_all = 0; | ||
313 | int bar_sz = s->bar_sz; | ||
314 | char *bar = s->bar; | ||
315 | int i; | ||
316 | |||
317 | if (rdval(get_file(&proc_stat), "cpu ", data, 1, 2, 3, 4, 5, 6, 7)) { | ||
318 | put_question_marks(bar_sz); | ||
319 | return; | ||
320 | } | ||
321 | |||
322 | for (i = 0; i < CPU_FIELDCNT; i++) { | ||
323 | ullong old = s->old[i]; | ||
324 | if (data[i] < old) old = data[i]; //sanitize | ||
325 | s->old[i] = data[i]; | ||
326 | all += (data[i] -= old); | ||
327 | } | ||
328 | |||
329 | if (all) { | ||
330 | for (i = 0; i < CPU_FIELDCNT; i++) { | ||
331 | ullong t = bar_sz * data[i]; | ||
332 | norm_all += data[i] = t / all; | ||
333 | frac[i] = t % all; | ||
334 | } | ||
335 | |||
336 | while (norm_all < bar_sz) { | ||
337 | unsigned max = frac[0]; | ||
338 | int pos = 0; | ||
339 | for (i = 1; i < CPU_FIELDCNT; i++) { | ||
340 | if (frac[i] > max) max = frac[i], pos = i; | ||
341 | } | ||
342 | frac[pos] = 0; //avoid bumping up same value twice | ||
343 | data[pos]++; | ||
344 | norm_all++; | ||
345 | } | ||
346 | |||
347 | memset(bar, '.', bar_sz); | ||
348 | memset(bar, 'S', data[2]); bar += data[2]; //sys | ||
349 | memset(bar, 'U', data[0]); bar += data[0]; //usr | ||
350 | memset(bar, 'N', data[1]); bar += data[1]; //nice | ||
351 | memset(bar, 'D', data[4]); bar += data[4]; //iowait | ||
352 | memset(bar, 'I', data[5]); bar += data[5]; //irq | ||
353 | memset(bar, 'i', data[6]); bar += data[6]; //softirq | ||
354 | } else { | ||
355 | memset(bar, '?', bar_sz); | ||
356 | } | ||
357 | put(s->bar); | ||
358 | } | ||
359 | |||
360 | |||
361 | static s_stat* init_cpu(const char *param) | ||
362 | { | ||
363 | int sz; | ||
364 | cpu_stat *s = xmalloc(sizeof(cpu_stat)); | ||
365 | s->collect = collect_cpu; | ||
366 | sz = strtol(param, NULL, 0); | ||
367 | if (sz < 10) sz = 10; | ||
368 | if (sz > 1000) sz = 1000; | ||
369 | s->bar = xmalloc(sz+1); | ||
370 | s->bar[sz] = '\0'; | ||
371 | s->bar_sz = sz; | ||
372 | return (s_stat*)s; | ||
373 | } | ||
374 | |||
375 | |||
376 | S_STAT(int_stat) | ||
377 | ullong old; | ||
378 | int no; | ||
379 | S_STAT_END(int_stat) | ||
380 | |||
381 | static void collect_int(int_stat *s) | ||
382 | { | ||
383 | ullong data[1]; | ||
384 | ullong old; | ||
385 | |||
386 | if (rdval(get_file(&proc_stat), "intr", data, s->no)) { | ||
387 | put_question_marks(4); | ||
388 | return; | ||
389 | } | ||
390 | |||
391 | old = s->old; | ||
392 | if (data[0] < old) old = data[0]; //sanitize | ||
393 | s->old = data[0]; | ||
394 | scale(data[0] - old); | ||
395 | } | ||
396 | |||
397 | static s_stat* init_int(const char *param) | ||
398 | { | ||
399 | int_stat *s = xmalloc(sizeof(int_stat)); | ||
400 | s->collect = collect_int; | ||
401 | if (param[0]=='\0') { | ||
402 | s->no = 1; | ||
403 | } else { | ||
404 | int n = strtoul(param, NULL, 0); | ||
405 | s->no = n+2; | ||
406 | } | ||
407 | return (s_stat*)s; | ||
408 | } | ||
409 | |||
410 | |||
411 | S_STAT(ctx_stat) | ||
412 | ullong old; | ||
413 | S_STAT_END(ctx_stat) | ||
414 | |||
415 | static void collect_ctx(ctx_stat *s) | ||
416 | { | ||
417 | ullong data[1]; | ||
418 | ullong old; | ||
419 | |||
420 | if (rdval(get_file(&proc_stat), "ctxt", data, 1)) { | ||
421 | put_question_marks(4); | ||
422 | return; | ||
423 | } | ||
424 | |||
425 | old = s->old; | ||
426 | if (data[0] < old) old = data[0]; //sanitize | ||
427 | s->old = data[0]; | ||
428 | scale(data[0] - old); | ||
429 | } | ||
430 | |||
431 | static s_stat* init_ctx(const char *param) | ||
432 | { | ||
433 | ctx_stat *s = xmalloc(sizeof(ctx_stat)); | ||
434 | s->collect = collect_ctx; | ||
435 | return (s_stat*)s; | ||
436 | } | ||
437 | |||
438 | |||
439 | S_STAT(blk_stat) | ||
440 | const char* lookfor; | ||
441 | ullong old[2]; | ||
442 | S_STAT_END(blk_stat) | ||
443 | |||
444 | static void collect_blk(blk_stat *s) | ||
445 | { | ||
446 | ullong data[2]; | ||
447 | int i; | ||
448 | |||
449 | if (is26) { | ||
450 | i = rdval_diskstats(get_file(&proc_diskstats), data); | ||
451 | } else { | ||
452 | i = rdval(get_file(&proc_stat), s->lookfor, data, 1, 2); | ||
453 | // Linux 2.4 reports bio in Kbytes, convert to sectors: | ||
454 | data[0] *= 2; | ||
455 | data[1] *= 2; | ||
456 | } | ||
457 | if (i) { | ||
458 | put_question_marks(9); | ||
459 | return; | ||
460 | } | ||
461 | |||
462 | for (i=0; i<2; i++) { | ||
463 | ullong old = s->old[i]; | ||
464 | if (data[i] < old) old = data[i]; //sanitize | ||
465 | s->old[i] = data[i]; | ||
466 | data[i] -= old; | ||
467 | } | ||
468 | scale(data[0]*512); // TODO: *sectorsize | ||
469 | put_c(' '); | ||
470 | scale(data[1]*512); | ||
471 | } | ||
472 | |||
473 | static s_stat* init_blk(const char *param) | ||
474 | { | ||
475 | blk_stat *s = xmalloc(sizeof(blk_stat)); | ||
476 | s->collect = collect_blk; | ||
477 | s->lookfor = "page"; | ||
478 | return (s_stat*)s; | ||
479 | } | ||
480 | |||
481 | |||
482 | S_STAT(fork_stat) | ||
483 | ullong old; | ||
484 | S_STAT_END(fork_stat) | ||
485 | |||
486 | static void collect_thread_nr(fork_stat *s) | ||
487 | { | ||
488 | ullong data[1]; | ||
489 | |||
490 | if (rdval_loadavg(get_file(&proc_loadavg), data, 4)) { | ||
491 | put_question_marks(4); | ||
492 | return; | ||
493 | } | ||
494 | scale(data[0]); | ||
495 | } | ||
496 | |||
497 | static void collect_fork(fork_stat *s) | ||
498 | { | ||
499 | ullong data[1]; | ||
500 | ullong old; | ||
501 | |||
502 | if (rdval(get_file(&proc_stat), "processes", data, 1)) { | ||
503 | put_question_marks(4); | ||
504 | return; | ||
505 | } | ||
506 | |||
507 | old = s->old; | ||
508 | if (data[0] < old) old = data[0]; //sanitize | ||
509 | s->old = data[0]; | ||
510 | scale(data[0] - old); | ||
511 | } | ||
512 | |||
513 | static s_stat* init_fork(const char *param) | ||
514 | { | ||
515 | fork_stat *s = xmalloc(sizeof(fork_stat)); | ||
516 | if (*param == 'n') { | ||
517 | s->collect = collect_thread_nr; | ||
518 | } else { | ||
519 | s->collect = collect_fork; | ||
520 | } | ||
521 | return (s_stat*)s; | ||
522 | } | ||
523 | |||
524 | |||
525 | S_STAT(if_stat) | ||
526 | ullong old[4]; | ||
527 | const char *device; | ||
528 | char *device_colon; | ||
529 | S_STAT_END(if_stat) | ||
530 | |||
531 | static void collect_if(if_stat *s) | ||
532 | { | ||
533 | ullong data[4]; | ||
534 | int i; | ||
535 | |||
536 | if (rdval(get_file(&proc_net_dev), s->device_colon, data, 1, 3, 9, 11)) { | ||
537 | put_question_marks(10); | ||
538 | return; | ||
539 | } | ||
540 | |||
541 | for (i=0; i<4; i++) { | ||
542 | ullong old = s->old[i]; | ||
543 | if (data[i] < old) old = data[i]; //sanitize | ||
544 | s->old[i] = data[i]; | ||
545 | data[i] -= old; | ||
546 | } | ||
547 | put_c(data[1] ? '*' : ' '); | ||
548 | scale(data[0]); | ||
549 | put_c(data[3] ? '*' : ' '); | ||
550 | scale(data[2]); | ||
551 | } | ||
552 | |||
553 | static s_stat* init_if(const char *device) | ||
554 | { | ||
555 | if_stat *s = xmalloc(sizeof(if_stat)); | ||
556 | |||
557 | if (!device || !device[0]) | ||
558 | bb_show_usage(); | ||
559 | s->collect = collect_if; | ||
560 | |||
561 | s->device = device; | ||
562 | s->device_colon = xmalloc(strlen(device)+2); | ||
563 | strcpy(s->device_colon, device); | ||
564 | strcat(s->device_colon, ":"); | ||
565 | return (s_stat*)s; | ||
566 | } | ||
567 | |||
568 | |||
569 | S_STAT(mem_stat) | ||
570 | char opt; | ||
571 | S_STAT_END(mem_stat) | ||
572 | |||
573 | // "Memory" value should not include any caches. | ||
574 | // IOW: neither "ls -laR /" nor heavy read/write activity | ||
575 | // should affect it. We'd like to also include any | ||
576 | // long-term allocated kernel-side mem, but it is hard | ||
577 | // to figure out. For now, bufs, cached & slab are | ||
578 | // counted as "free" memory | ||
579 | //2.6.16: | ||
580 | //MemTotal: 773280 kB | ||
581 | //MemFree: 25912 kB - genuinely free | ||
582 | //Buffers: 320672 kB - cache | ||
583 | //Cached: 146396 kB - cache | ||
584 | //SwapCached: 0 kB | ||
585 | //Active: 183064 kB | ||
586 | //Inactive: 356892 kB | ||
587 | //HighTotal: 0 kB | ||
588 | //HighFree: 0 kB | ||
589 | //LowTotal: 773280 kB | ||
590 | //LowFree: 25912 kB | ||
591 | //SwapTotal: 131064 kB | ||
592 | //SwapFree: 131064 kB | ||
593 | //Dirty: 48 kB | ||
594 | //Writeback: 0 kB | ||
595 | //Mapped: 96620 kB | ||
596 | //Slab: 200668 kB - takes 7 Mb on my box fresh after boot, | ||
597 | // but includes dentries and inodes | ||
598 | // (== can take arbitrary amount of mem) | ||
599 | //CommitLimit: 517704 kB | ||
600 | //Committed_AS: 236776 kB | ||
601 | //PageTables: 1248 kB | ||
602 | //VmallocTotal: 516052 kB | ||
603 | //VmallocUsed: 3852 kB | ||
604 | //VmallocChunk: 512096 kB | ||
605 | //HugePages_Total: 0 | ||
606 | //HugePages_Free: 0 | ||
607 | //Hugepagesize: 4096 kB | ||
608 | static void collect_mem(mem_stat *s) | ||
609 | { | ||
610 | ullong m_total = 0; | ||
611 | ullong m_free = 0; | ||
612 | ullong m_bufs = 0; | ||
613 | ullong m_cached = 0; | ||
614 | ullong m_slab = 0; | ||
615 | |||
616 | if (rdval(get_file(&proc_meminfo), "MemTotal:", &m_total, 1)) { | ||
617 | put_question_marks(4); | ||
618 | return; | ||
619 | } | ||
620 | if (s->opt == 'f') { | ||
621 | scale(m_total << 10); | ||
622 | return; | ||
623 | } | ||
624 | |||
625 | if (rdval(proc_meminfo.file, "MemFree:", &m_free , 1) | ||
626 | || rdval(proc_meminfo.file, "Buffers:", &m_bufs , 1) | ||
627 | || rdval(proc_meminfo.file, "Cached:", &m_cached, 1) | ||
628 | || rdval(proc_meminfo.file, "Slab:", &m_slab , 1) | ||
629 | ) { | ||
630 | put_question_marks(4); | ||
631 | return; | ||
632 | } | ||
633 | |||
634 | m_free += m_bufs + m_cached + m_slab; | ||
635 | switch (s->opt) { | ||
636 | case 'f': | ||
637 | scale(m_free << 10); break; | ||
638 | default: | ||
639 | scale((m_total - m_free) << 10); break; | ||
640 | } | ||
641 | } | ||
642 | |||
643 | static s_stat* init_mem(const char *param) | ||
644 | { | ||
645 | mem_stat *s = xmalloc(sizeof(mem_stat)); | ||
646 | s->collect = collect_mem; | ||
647 | s->opt = param[0]; | ||
648 | return (s_stat*)s; | ||
649 | } | ||
650 | |||
651 | |||
652 | S_STAT(swp_stat) | ||
653 | S_STAT_END(swp_stat) | ||
654 | |||
655 | static void collect_swp(swp_stat *s) | ||
656 | { | ||
657 | ullong s_total[1]; | ||
658 | ullong s_free[1]; | ||
659 | if (rdval(get_file(&proc_meminfo), "SwapTotal:", s_total, 1) | ||
660 | || rdval(proc_meminfo.file, "SwapFree:" , s_free, 1) | ||
661 | ) { | ||
662 | put_question_marks(4); | ||
663 | return; | ||
664 | } | ||
665 | scale((s_total[0]-s_free[0]) << 10); | ||
666 | } | ||
667 | |||
668 | static s_stat* init_swp(const char *param) | ||
669 | { | ||
670 | swp_stat *s = xmalloc(sizeof(swp_stat)); | ||
671 | s->collect = collect_swp; | ||
672 | return (s_stat*)s; | ||
673 | } | ||
674 | |||
675 | |||
676 | S_STAT(fd_stat) | ||
677 | S_STAT_END(fd_stat) | ||
678 | |||
679 | static void collect_fd(fd_stat *s) | ||
680 | { | ||
681 | ullong data[2]; | ||
682 | |||
683 | if (rdval(get_file(&proc_sys_fs_filenr), "", data, 1, 2)) { | ||
684 | put_question_marks(4); | ||
685 | return; | ||
686 | } | ||
687 | |||
688 | scale(data[0] - data[1]); | ||
689 | } | ||
690 | |||
691 | static s_stat* init_fd(const char *param) | ||
692 | { | ||
693 | fd_stat *s = xmalloc(sizeof(fd_stat)); | ||
694 | s->collect = collect_fd; | ||
695 | return (s_stat*)s; | ||
696 | } | ||
697 | |||
698 | |||
699 | S_STAT(time_stat) | ||
700 | int prec; | ||
701 | int scale; | ||
702 | S_STAT_END(time_stat) | ||
703 | |||
704 | static void collect_time(time_stat *s) | ||
705 | { | ||
706 | char buf[sizeof("12:34:56.123456")]; | ||
707 | struct tm* tm; | ||
708 | int us = tv.tv_usec + s->scale/2; | ||
709 | time_t t = tv.tv_sec; | ||
710 | |||
711 | if (us >= 1000000) { | ||
712 | t++; | ||
713 | us -= 1000000; | ||
714 | } | ||
715 | tm = localtime(&t); | ||
716 | |||
717 | sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); | ||
718 | if (s->prec) | ||
719 | sprintf(buf+8, ".%0*d", s->prec, us / s->scale); | ||
720 | put(buf); | ||
721 | } | ||
722 | |||
723 | static s_stat* init_time(const char *param) | ||
724 | { | ||
725 | int prec; | ||
726 | time_stat *s = xmalloc(sizeof(time_stat)); | ||
727 | |||
728 | s->collect = collect_time; | ||
729 | prec = param[0]-'0'; | ||
730 | if (prec < 0) prec = 0; | ||
731 | else if (prec > 6) prec = 6; | ||
732 | s->prec = prec; | ||
733 | s->scale = 1; | ||
734 | while (prec++ < 6) | ||
735 | s->scale *= 10; | ||
736 | return (s_stat*)s; | ||
737 | } | ||
738 | |||
739 | static void collect_info(s_stat *s) | ||
740 | { | ||
741 | gen ^= 1; | ||
742 | while (s) { | ||
743 | put(s->label); | ||
744 | s->collect(s); | ||
745 | s = s->next; | ||
746 | } | ||
747 | } | ||
748 | |||
749 | |||
750 | typedef s_stat* init_func(const char *param); | ||
751 | |||
752 | static const char options[] = "ncmsfixptbdr"; | ||
753 | static init_func *const init_functions[] = { | ||
754 | init_if, | ||
755 | init_cpu, | ||
756 | init_mem, | ||
757 | init_swp, | ||
758 | init_fd, | ||
759 | init_int, | ||
760 | init_ctx, | ||
761 | init_fork, | ||
762 | init_time, | ||
763 | init_blk, | ||
764 | init_delay, | ||
765 | init_cr, | ||
766 | }; | ||
767 | |||
768 | int nmeter_main(int argc, char **argv); | ||
769 | int nmeter_main(int argc, char **argv) | ||
770 | { | ||
771 | char buf[32]; | ||
772 | s_stat *first = NULL; | ||
773 | s_stat *last = NULL; | ||
774 | s_stat *s; | ||
775 | char *cur, *prev; | ||
776 | |||
777 | INIT_G(); | ||
778 | |||
779 | xchdir("/proc"); | ||
780 | |||
781 | if (argc != 2) | ||
782 | bb_show_usage(); | ||
783 | |||
784 | if (open_read_close("version", buf, sizeof(buf)) > 0) | ||
785 | is26 = (strstr(buf, " 2.4.")==NULL); | ||
786 | |||
787 | // Can use argv[1] directly, but this will mess up | ||
788 | // parameters as seen by e.g. ps. Making a copy... | ||
789 | cur = xstrdup(argv[1]); | ||
790 | while (1) { | ||
791 | char *param, *p; | ||
792 | prev = cur; | ||
793 | again: | ||
794 | cur = strchr(cur, '%'); | ||
795 | if (!cur) | ||
796 | break; | ||
797 | if (cur[1] == '%') { // %% | ||
798 | strcpy(cur, cur+1); | ||
799 | cur++; | ||
800 | goto again; | ||
801 | } | ||
802 | *cur++ = '\0'; // overwrite % | ||
803 | if (cur[0] == '[') { | ||
804 | // format: %[foptstring] | ||
805 | cur++; | ||
806 | p = strchr(options, cur[0]); | ||
807 | param = cur+1; | ||
808 | while (cur[0] != ']') { | ||
809 | if (!cur[0]) | ||
810 | bb_show_usage(); | ||
811 | cur++; | ||
812 | } | ||
813 | *cur++ = '\0'; // overwrite [ | ||
814 | } else { | ||
815 | // format: %NNNNNNf | ||
816 | param = cur; | ||
817 | while (cur[0] >= '0' && cur[0] <= '9') | ||
818 | cur++; | ||
819 | if (!cur[0]) | ||
820 | bb_show_usage(); | ||
821 | p = strchr(options, cur[0]); | ||
822 | *cur++ = '\0'; // overwrite format char | ||
823 | } | ||
824 | if (!p) | ||
825 | bb_show_usage(); | ||
826 | s = init_functions[p-options](param); | ||
827 | if (s) { | ||
828 | s->label = prev; | ||
829 | s->next = 0; | ||
830 | if (!first) | ||
831 | first = s; | ||
832 | else | ||
833 | last->next = s; | ||
834 | last = s; | ||
835 | } else { | ||
836 | // %NNNNd or %r option. remove it from string | ||
837 | strcpy(prev + strlen(prev), cur); | ||
838 | cur = prev; | ||
839 | } | ||
840 | } | ||
841 | if (prev[0]) { | ||
842 | s = init_literal(); | ||
843 | s->label = prev; | ||
844 | s->next = 0; | ||
845 | if (!first) | ||
846 | first = s; | ||
847 | else | ||
848 | last->next = s; | ||
849 | last = s; | ||
850 | } | ||
851 | |||
852 | // Generate first samples but do not print them, they're bogus | ||
853 | collect_info(first); | ||
854 | reset_outbuf(); | ||
855 | if (delta >= 0) { | ||
856 | gettimeofday(&tv, NULL); | ||
857 | usleep(delta > 1000000 ? 1000000 : delta - tv.tv_usec%deltanz); | ||
858 | } | ||
859 | |||
860 | while (1) { | ||
861 | gettimeofday(&tv, NULL); | ||
862 | collect_info(first); | ||
863 | put(final_str); | ||
864 | print_outbuf(); | ||
865 | |||
866 | // Negative delta -> no usleep at all | ||
867 | // This will hog the CPU but you can have REALLY GOOD | ||
868 | // time resolution ;) | ||
869 | // TODO: detect and avoid useless updates | ||
870 | // (like: nothing happens except time) | ||
871 | if (delta >= 0) { | ||
872 | int rem; | ||
873 | // can be commented out, will sacrifice sleep time precision a bit | ||
874 | gettimeofday(&tv, NULL); | ||
875 | if (need_seconds) | ||
876 | rem = delta - ((ullong)tv.tv_sec*1000000 + tv.tv_usec) % deltanz; | ||
877 | else | ||
878 | rem = delta - tv.tv_usec%deltanz; | ||
879 | // Sometimes kernel wakes us up just a tiny bit earlier than asked | ||
880 | // Do not go to very short sleep in this case | ||
881 | if (rem < delta/128) { | ||
882 | rem += delta; | ||
883 | } | ||
884 | usleep(rem); | ||
885 | } | ||
886 | } | ||
887 | |||
888 | /*return 0;*/ | ||
889 | } | ||
diff --git a/procps/watch.c b/procps/watch.c new file mode 100644 index 000000000..2ad0564cd --- /dev/null +++ b/procps/watch.c | |||
@@ -0,0 +1,80 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * Mini watch implementation for busybox | ||
4 | * | ||
5 | * Copyright (C) 2001 by Michael Habermann <mhabermann@gmx.de> | ||
6 | * Copyrigjt (C) Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) | ||
7 | * | ||
8 | * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. | ||
9 | */ | ||
10 | |||
11 | /* BB_AUDIT SUSv3 N/A */ | ||
12 | /* BB_AUDIT GNU defects -- only option -n is supported. */ | ||
13 | |||
14 | #include "libbb.h" | ||
15 | |||
16 | // procps 2.0.18: | ||
17 | // watch [-d] [-n seconds] | ||
18 | // [--differences[=cumulative]] [--interval=seconds] command | ||
19 | // | ||
20 | // procps-3.2.3: | ||
21 | // watch [-dt] [-n seconds] | ||
22 | // [--differences[=cumulative]] [--interval=seconds] [--no-title] command | ||
23 | // | ||
24 | // (procps 3.x and procps 2.x are forks, not newer/older versions of the same) | ||
25 | |||
26 | int watch_main(int argc, char **argv); | ||
27 | int watch_main(int argc, char **argv) | ||
28 | { | ||
29 | unsigned opt; | ||
30 | unsigned period = 2; | ||
31 | unsigned cmdlen = 1; // 1 for terminal NUL | ||
32 | char *header = NULL; | ||
33 | char *cmd; | ||
34 | char *tmp; | ||
35 | char **p; | ||
36 | |||
37 | opt_complementary = "-1"; // at least one param please | ||
38 | opt = getopt32(argc, argv, "+dtn:", &tmp); | ||
39 | //if (opt & 0x1) // -d (ignore) | ||
40 | //if (opt & 0x2) // -t | ||
41 | if (opt & 0x4) period = xatou(tmp); | ||
42 | argv += optind; | ||
43 | |||
44 | p = argv; | ||
45 | while (*p) | ||
46 | cmdlen += strlen(*p++) + 1; | ||
47 | tmp = cmd = xmalloc(cmdlen); | ||
48 | while (*argv) { | ||
49 | tmp += sprintf(tmp, " %s", *argv); | ||
50 | argv++; | ||
51 | } | ||
52 | cmd++; // skip initial space | ||
53 | |||
54 | while (1) { | ||
55 | printf("\033[H\033[J"); | ||
56 | if (!(opt & 0x2)) { // no -t | ||
57 | int width, len; | ||
58 | char *thyme; | ||
59 | time_t t; | ||
60 | |||
61 | get_terminal_width_height(STDOUT_FILENO, &width, 0); | ||
62 | header = xrealloc(header, width--); | ||
63 | // '%-*s' pads header with spaces to the full width | ||
64 | snprintf(header, width, "Every %ds: %-*s", period, width, cmd); | ||
65 | time(&t); | ||
66 | thyme = ctime(&t); | ||
67 | len = strlen(thyme); | ||
68 | if (len < width) | ||
69 | strcpy(header + width - len, thyme); | ||
70 | puts(header); | ||
71 | } | ||
72 | fflush(stdout); | ||
73 | // TODO: 'real' watch pipes cmd's output to itself | ||
74 | // and does not allow it to overflow the screen | ||
75 | // (taking into account linewrap!) | ||
76 | system(cmd); | ||
77 | sleep(period); | ||
78 | } | ||
79 | return 0; // gcc thinks we can reach this :) | ||
80 | } | ||