aboutsummaryrefslogtreecommitdiff
path: root/toys
diff options
context:
space:
mode:
Diffstat (limited to 'toys')
-rw-r--r--toys/posix/ps.c252
1 files changed, 171 insertions, 81 deletions
diff --git a/toys/posix/ps.c b/toys/posix/ps.c
index b06be429..3b31d4b1 100644
--- a/toys/posix/ps.c
+++ b/toys/posix/ps.c
@@ -29,6 +29,12 @@
* output argv[0] unmodified for -o comm or -o args (but procps violates
* posix for -o comm anyway, it's stat[2] not argv[0]).
*
+ * Note: iotop is STAYROOT so it can read other process's /proc/$PID/io
+ * files (why they're not globally readable when the rest of proc
+ * data is...?) and get a global I/O picture. Normal top is NOT,
+ * even though you can -o AIO there, to give sysadmins the option
+ * to reduce security exposure.)
+ *
* TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference)
* TODO: switch -fl to -y
* TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l")
@@ -37,8 +43,8 @@
USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
// stayroot because iotop needs root to read other process' proc/$$/io
-USE_TOP(NEWTOY(top, ">0m" "p*u*d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
-USE_IOTOP(NEWTOY(iotop, ">0Aako" "p*u*d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
+USE_TOP(NEWTOY(top, ">0m" "h:k*o*p*u*s#<1=9d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_IOTOP(NEWTOY(iotop, ">0AaKO" "h:k*o*p*u*s#<1=7d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:", TOYFLAG_USR|TOYFLAG_BIN))
USE_PKILL(NEWTOY(pkill, "Vu*U*t*s*P*g*G*fnovxl:", TOYFLAG_USR|TOYFLAG_BIN))
@@ -114,41 +120,44 @@ config TOP
help
usage: top [-m] [ -d seconds ] [ -n iterations ]
- Provide a view of process activity in real time.
- Keys
- N/M/P/T show CPU usage, sort by pid/mem/cpu/time
- S show memory
- R reverse sort
+ Show process activity in real time.
+
+ -h Header (default Tasks: %PID, %S=R running, %S=S sleeping, %S
+ -k Fallback sort FIELDS (default -S,-%CPU,-ETIME,-PID)
+ -o Show FIELDS (def PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,CMDLINE)
+ -s Sort by field number (1-X, default 9)
+
+ Default header is:
+ Tasks: %PID, %S=R= running, %S=S= sleeping, %S=T= stopped, %S=Z= zombie\n
+ Mem: %10KMEM total, %10KMUSED used, %10KMFREE free, %10KBUF buffers
+ Swap: %9KSWAP total, %10KSWUSED used, %10KSWFREE free, %10K
+
H toggle threads
C,1 toggle SMP
- Q,^C exit
-
- Options
- -n Iterations before exiting
- -d Delay between updates
- -m Same as 's' key
# Requires CONFIG_IRQ_TIME_ACCOUNTING in the kernel for /proc/$$/io
config IOTOP
bool "iotop"
default y
help
- usage: iotop [-Aako]
+ usage: iotop [-AaKO]
Rank processes by I/O.
-A All I/O, not just disk
-a Accumulated I/O (not percentage)
- -k Kilobytes
- -o Only show processes doing I/O
-
- Cursor left/right to change sort, space to update, Q to exit.
+ -K Kilobytes
+ -k Fallback sort FIELDS (default -[D]IO,-ETIME,-PID)
+ -O Only show processes doing I/O
+ -o Show FIELDS (default PID,PR,USER,[D]READ,[D]WRITE,SWAP,[D]IO,COMM)
+ -s Sort by field number (0-X, default 6)
config TOP_COMMON
bool
default y
help
- usage: COMMON [-bq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,]
+ usage: COMMON [-bq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,] [-s SORT]
+
-b Batch mode (no tty)
-d Delay SECONDS between each cycle (default 3)
-n Exit after NUMBER iterations
@@ -156,6 +165,9 @@ config TOP_COMMON
-u Show these USERs
-q Quiet (no header lines)
+ Cursor LEFT/RIGHT to change sort, UP/DOWN move list, space to force
+ update, R to reverse sort, Q to exit.
+
config PGREP
bool "pgrep"
default y
@@ -220,8 +232,12 @@ GLOBALS(
struct {
long n;
long d;
+ long s;
struct arg_list *u;
struct arg_list *p;
+ struct arg_list *o;
+ struct arg_list *k;
+ char *h;
} top;
struct{
char *L;
@@ -245,7 +261,7 @@ GLOBALS(
unsigned width, height;
dev_t tty;
void *fields, *kfields;
- long long ticks, bits, ioread, iowrite, aioread, aiowrite;
+ long long ticks, bits;
size_t header_len;
int kcount, forcek, sortpos;
int (*match_process)(long long *slot);
@@ -533,7 +549,6 @@ static void show_ps(struct carveup *tb)
else width -= printf("%*.*s", pad, len, out);
if (!width) break;
}
- xputc('\n');
}
// dirtree callback: read data about process to display, store, or discard it.
@@ -747,6 +762,7 @@ static int get_ps(struct dirtree *new)
if (TT.show_process) {
TT.show_process(tb);
+ xputc('\n');
return 0;
}
@@ -960,6 +976,18 @@ static struct carveup **collate(int count, struct dirtree *dt,
return tbsort;
}
+static void quick_ko(char *str, void *fields, char *err)
+{
+ struct arg_list al;
+
+ // Display fields
+ al.next = 0;
+ al.arg = xstrdup(str);
+ comma_args(&al, fields, err, parse_ko);
+ free(al.arg);
+ if (err != (char *)1) dlist_terminate(*(void **)fields);
+}
+
static void shared_main(void)
{
int i;
@@ -1005,11 +1033,7 @@ void ps_main(void)
// Parse manual field selection, or default/-f/-l, plus -Z,
// constructing the header line in toybuf as we go.
- if (toys.optflags&FLAG_Z) {
- struct arg_list Z = { 0, "LABEL" };
-
- comma_args(&Z, &TT.fields, "-Z", parse_ko);
- }
+ if (toys.optflags&FLAG_Z) quick_ko("LABEL", &TT.fields, (char *)1);
if (TT.ps.o) comma_args(TT.ps.o, &TT.fields, "bad -o field", parse_ko);
else {
struct arg_list al;
@@ -1056,6 +1080,7 @@ void ps_main(void)
for (i = 0; i<TT.kcount; i++) {
show_ps(tbsort[i]);
free(tbsort[i]);
+ xputc('\n');
}
if (CFG_TOYBOX_FREE) free(tbsort);
}
@@ -1086,7 +1111,7 @@ static void setsort(int pos)
if (pos<0) pos = 0;
for (field = TT.fields; field; field = field->next) {
- if ((TT.sortpos = i++)<pos) continue;
+ if ((TT.sortpos = i++)<pos && field->next) continue;
going2 = TT.kfields;
going2->which = field->which;
going2->len = field->len;
@@ -1109,7 +1134,7 @@ static int merge_deltas(long long *oslot, long long *nslot)
return 1;
}
-static void top_common(char *header,
+static void top_common(char *intro, char *header,
int (*filter)(long long *oslot, long long *nslot))
{
struct timespec ts;
@@ -1120,21 +1145,9 @@ static void top_common(char *header,
} plist[2], *plold, *plnew, old, new, mix;
char scratch[16];
unsigned tock = 0;
- int i, lines, done = 0;
+ int i, lines, topoff = 0, done = 0;
*scratch = 0;
- TT.top.d *= 1000;
- if (toys.optflags&FLAG_b) TT.width = TT.height = 99999;
- else {
- xset_terminal(0, 1, 0);
- sigatexit(tty_sigreset);
- }
- shared_main();
-
- comma_args(TT.top.u, &TT.uu, "bad -u", parse_rest);
- comma_args(TT.top.p, &TT.pp, "bad -p", parse_rest);
-
- TT.match_process = shared_match_process;
memset(plist, 0, sizeof(plist));
do {
struct dirtree *dt = dirtree_read("/proc", get_ps);
@@ -1182,18 +1195,76 @@ static void top_common(char *header,
new.count--;
}
- // Will will re-fetch no data before its time. - Mork calling Orson Welles
+ // We will re-fetch no data before its time. - Mork calling Orson Welles
for (;;) {
- char was, is, *pos;
+ int and, or, not, xor;
+ char *pos, *pct, *end=end, buf[32], was, is;
qsort(mix.tb, mix.count, sizeof(struct carveup *), (void *)ksort);
-printf("cheese\n");
+ lines = TT.height;
if (!(toys.optflags&FLAG_b)) printf("\033[H\033[J");
if (!(toys.optflags&FLAG_q)) {
+
+ // Show intro lines, parsing printf-ish escapes
i = 0;
- strcpy(pos = toybuf, header);
+ pos = end = TT.top.h ? TT.top.h : intro;
+ for (;;) {
+ if (i==sizeof(toybuf)-8) error_exit("tilt");
+ toybuf[i] = 0;
+ if (end && end<=pos) end = next_printf(pos, &pct);
+
+ // Not an %escape sequence?
+ if (!end || pct>pos) {
+ char c = *pos;
+
+ if (c=='\\') {
+ if ((or = unescape(*++pos))) c = or;
+ else if (!*pos) error_exit("bad -h \\");
+ }
- for (i=0, is = *pos; *pos; pos++) {
+ if (c=='\t') {
+ and = 8-utf8len(toybuf);
+ while (and--) toybuf[i++] = ' ';
+ } else if (!c || '\n'==c) {
+ draw_str(toybuf, TT.width);
+ putchar('\r');
+ putchar('\n');
+ if (1>--lines) break;
+ i = 0;
+ if (!c) break;
+ } else toybuf[i++] = c;
+
+ // Escape sequences
+ } else if (pct) {
+ not = -1;
+ for (and = or = 0; and < ARRAY_LEN(typos); and++) {
+ xor = strlen(typos[and].name);
+ if (xor>or && !strncasecmp(typos[and].name, end, xor)) {
+ not = and;
+ or = xor;
+ }
+ }
+ if (!or) error_exit("bad -h '%.16ss'", end);
+ snprintf(buf, sizeof(buf)-1, "%.*s", (int)(end-pct), pct);
+ strcat(buf, "s");
+ pos = (end += or);
+
+ or = *pos;
+ if (or == '=') {
+ for (pos++; *pos && *pos != '='; pos++);
+ if (!*pos++) error_exit("-h bad =");
+ }
+ // i += snprintf(toybuf+i, sizeof(toybuf)-i, "[%s]", buf);
+ i += snprintf(toybuf+i, sizeof(toybuf)-i, "{%d}", not);
+ // for (and = 0; and<mix.count; and++) { }
+
+ pos--;
+ }
+ pos++;
+ }
+
+ strcpy(pos = toybuf, header);
+ for (i = 0, is = *pos; *pos; pos++) {
was = is;
is = *pos;
if (isspace(was) && !isspace(is) && i++==TT.sortpos) pos[-1] = '[';
@@ -1202,15 +1273,15 @@ printf("cheese\n");
*pos = 0;
printf("\033[7m%*.*s\033[0m\n\r",
(toys.optflags&FLAG_b) ? 0 : -TT.width, TT.width, toybuf);
+ lines--;
if (!(toys.optflags&FLAG_b))
terminal_probesize(&TT.width, &TT.height);
}
- lines = TT.height-2;
- for (i=0; i<lines && i<mix.count; i++) {
- show_ps(mix.tb[i]);
- xputc('\r');
+ for (i = 0; i<lines && i+topoff<mix.count; i++) {
+ if (i) xprintf("\r\n");
+ show_ps(mix.tb[i+topoff]);
}
if (TT.top.n && !--TT.top.n) {
@@ -1236,10 +1307,15 @@ printf("cheese\n");
else if (i==' ') {
timeout = now;
break;
- } else {
+ } else if (toupper(i)=='R')
+ ((struct strawberry *)TT.kfields)->reverse *= -1;
+ else {
i -= 256;
if (i == KEY_LEFT) setsort(TT.sortpos-1);
else if (i == KEY_RIGHT) setsort(TT.sortpos+1);
+ else if (i == KEY_UP) {
+ if (topoff) topoff--;
+ } else if (i == KEY_DOWN) topoff++;
}
continue;
}
@@ -1251,26 +1327,49 @@ printf("cheese\n");
if (!(toys.optflags&FLAG_b)) tty_reset();
}
-void top_main(void)
+static char *top_setup(char *defo, char *defk)
{
- struct arg_list al;
char *header;
+ int len;
- // Display fields
- al.next = 0;
- al.arg = xstrdup("PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,CMDLINE");
- comma_args(&al, &TT.fields, 0, parse_ko);
- free(al.arg);
+ TT.top.d *= 1000;
+ if (toys.optflags&FLAG_b) TT.width = TT.height = 99999;
+ else {
+ xset_terminal(0, 1, 0);
+ sigatexit(tty_sigreset);
+ }
+ shared_main();
+
+ comma_args(TT.top.u, &TT.uu, "bad -u", parse_rest);
+ comma_args(TT.top.p, &TT.pp, "bad -p", parse_rest);
+ TT.match_process = shared_match_process;
+
+ if (TT.top.o) comma_args(TT.top.o, &TT.fields, "-o", parse_ko);
+ else quick_ko(defo, &TT.fields, (char *)1);
dlist_terminate(TT.fields);
+ len = strlen(toybuf);
+ if (toybuf[len-1]!=' ' && len<sizeof(toybuf)-1) strcpy(toybuf+len, " ");
header=xstrdup(toybuf);
- // Fallback sorts
- al.arg = xstrdup("-S,-%CPU,-ETIME,-PID");
- comma_args(&al, &TT.kfields, "bang", parse_ko);
+ // First (dummy) sort field is overwritten by setsort()
+ quick_ko("-S", &TT.kfields, (char *)1);
+ if (TT.top.k) comma_args(TT.top.k, &TT.kfields, "-k", parse_ko);
+ else quick_ko(defk, &TT.kfields, (char *)1);
dlist_terminate(TT.kfields);
- setsort(8);
+ setsort(TT.top.s-1);
+
+ return header;
+}
+
+void top_main(void)
+{
+ // usage: [-h HEADER] -o OUTPUT -k SORT
+
+ char *header = top_setup(
+ "PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,CMDLINE",
+ "-%CPU,-ETIME,-PID");
- top_common(header, merge_deltas);
+ top_common("top", header, merge_deltas);
}
#define CLEANUP_top
@@ -1281,31 +1380,22 @@ static int iotop_filter(long long *oslot, long long *nslot)
{
if (!(toys.optflags&FLAG_a)) merge_deltas(oslot, nslot);
- return !(toys.optflags&FLAG_o) || oslot[SLOT_iobytes+!(toys.optflags&FLAG_A)];
+ return !(toys.optflags&FLAG_o)||oslot[SLOT_iobytes+!(toys.optflags&FLAG_A)];
}
void iotop_main(void)
{
- struct arg_list al;
- char *header, *d = "D"+!!(toys.optflags&FLAG_A);
+ char *s1 = 0, *s2 = 0, *header, *d = "D"+!!(toys.optflags&FLAG_A);
- if (toys.optflags&FLAG_k) TT.forcek++;
+ if (toys.optflags&FLAG_K) TT.forcek++;
- al.next = 0;
- al.arg = xmprintf("PID,PR,USER,%sREAD,%sWRITE,SWAP,%sIO,COMM", d, d, d);
- comma_args(&al, &TT.fields, 0, parse_ko);
- free(al.arg);
- dlist_terminate(TT.fields);
- header = strdup(toybuf);
-
- // Fallback sorts. First (dummy) field gets overwritten by setsort()
- al.arg = xmprintf("-S,-%sIO,-ETIME,-PID",d);
- comma_args(&al, &TT.kfields, 0, parse_ko);
- free(al.arg);
- dlist_terminate(TT.kfields);
- setsort(6);
+ header = top_setup(
+ s1 = xmprintf("PID,PR,USER,%sREAD,%sWRITE,SWAP,%sIO,COMM", d, d, d),
+ s2 = xmprintf("-%sIO,-ETIME,-PID",d));
+ free(s1);
+ free(s2);
- top_common(header, iotop_filter);
+ top_common("iotop", header, iotop_filter);
}
// pkill's plumbing wrap's pgrep's and thus mostly takes place in pgrep's flag