USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflMno*O*p(pid)*s*t*Tu*U*g*G*wZ[!ol][+Ae][!oO]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
// stayroot because iotop needs root to read other process' proc/$$/io
-USE_TOP(NEWTOY(top, ">0m" "O*Hk*o*p*u*s#<1d#=3<1n#<1bq[!oO]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
-USE_IOTOP(NEWTOY(iotop, ">0AaKO" "k*o*p*u*s#<1=7d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
+// TOP and IOTOP have a large common option block used for common processing,
+// the default values are different but the flags are in the same order.
+USE_TOP(NEWTOY(top, ">0O*" "Hk*o*p*u*s#<1d#=3<1m#n#<1bq[!oO]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_IOTOP(NEWTOY(iotop, ">0AaKO" "Hk*o*p*u*s#<1=7d#=3<1m#n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE))
USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
USE_PKILL(NEWTOY(pkill, "?Vu*U*t*s*P*g*G*fnovxl:[-no]", TOYFLAG_USR|TOYFLAG_BIN))
config TOP
bool "top"
- depends on TOP_COMMON
default y
help
- usage: top [-H] [-k FIELD,] [-o FIELD,] [-s SORT]
+ usage: top [-Hbq] [-k FIELD,] [-o FIELD,] [-s SORT] [-n NUMBER] [-m LINES] [-d SECONDS] [-p PID,] [-u USER,]
Show process activity in real time.
-o Show FIELDS (def PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,CMDLINE)
-O Add FIELDS (replacing PR,NI,VIRT,RES,SHR,S from default)
-s Sort by field number (1-X, default 9)
+ -b Batch mode (no tty)
+ -d Delay SECONDS between each cycle (default 3)
+ -m Maximum number of tasks to show
+ -n Exit after NUMBER iterations
+ -p Show these PIDs
+ -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.
# Requires CONFIG_IRQ_TIME_ACCOUNTING in the kernel for /proc/$$/io
config IOTOP
bool "iotop"
- depends on TOP_COMMON
default y
help
- usage: iotop [-AaKO]
+ usage: iotop [-AaKObq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,]
Rank processes by I/O.
-A All I/O, not just disk
-a Accumulated I/O (not percentage)
+ -H Show threads
-K Kilobytes
-k Fallback sort FIELDS (default -[D]IO,-ETIME,-PID)
+ -m Maximum number of tasks to show
-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: * [-bq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,]
-
-b Batch mode (no tty)
-d Delay SECONDS between each cycle (default 3)
-n Exit after NUMBER iterations
config PGREP
bool "pgrep"
default y
- depends on PGKILL_COMMON
help
- usage: pgrep [-cl] [-d DELIM] [-L SIGNAL] [PATTERN]
+ usage: pgrep [-clfnovx] [-d DELIM] [-L SIGNAL] [PATTERN] [-G GID,] [-g PGRP,] [-P PPID,] [-s SID,] [-t TERM,] [-U UID,] [-u EUID,]
Search for process(es). PATTERN is an extended regular expression checked
against command names.
-d Use DELIM instead of newline
-L Send SIGNAL instead of printing name
-l Show command name
+ -f Check full command line for PATTERN
+ -G Match real Group ID(s)
+ -g Match Process Group(s) (0 is current user)
+ -n Newest match only
+ -o Oldest match only
+ -P Match Parent Process ID(s)
+ -s Match Session ID(s) (0 for current)
+ -t Match Terminal(s)
+ -U Match real User ID(s)
+ -u Match effective User ID(s)
+ -v Negate the match
+ -x Match whole command (not substring)
config PKILL
bool "pkill"
default y
- depends on PGKILL_COMMON
help
- usage: pkill [-SIGNAL|-l SIGNAL] [PATTERN]
+ usage: pkill [-fnovx] [-SIGNAL|-l SIGNAL] [PATTERN] [-G GID,] [-g PGRP,] [-P PPID,] [-s SID,] [-t TERM,] [-U UID,] [-u EUID,]
-l Send SIGNAL (default SIGTERM)
-V verbose
-
-config PGKILL_COMMON
- bool
- default y
- help
- usage: * [-fnovx] [-G GID,] [-g PGRP,] [-P PPID,] [-s SID,] [-t TERM,] [-U UID,] [-u EUID,]
-
-f Check full command line for PATTERN
-G Match real Group ID(s)
-g Match Process Group(s) (0 is current user)
} ps;
struct {
long n;
+ long m;
long d;
long s;
struct arg_list *u;
void (*show_process)(void *tb);
)
+/* Linked list of fields selected for display, in order, with :len and =title */
+
struct strawberry {
struct strawberry *next, *prev;
short which, len, reverse;
char forever[];
};
-/* The slot[] array is mostly populated from /proc/$PID/stat (kernel proc.txt
- * table 1-4) but we shift and repurpose fields, with the result being: */
+/* The function get_ps() reads all the data about one process, saving it in
+ * toybox as a struct carveup. Simple ps calls then pass toybuf directly to
+ * show_ps(), but features like sorting instead append a copy to a linked list
+ * for further processing once all processes have been read.
+ *
+ * struct carveup contains a slot[] array of 64 bit values, with the following
+ * data at each position in the array. Most is read from /proc/$PID/stat (see
+ * https://kernel.org/doc/Documentation/filesystems/proc.txt table 1-4) but
+ * we we replace several fields with don't use with other data. */
enum {
SLOT_pid, /*process id*/ SLOT_ppid, // parent process id
SLOT_flags, /*task flags*/ SLOT_minflt, // minor faults
SLOT_cminflt, /*minor faults+child*/ SLOT_majflt, // major faults
SLOT_cmajflt, /*major faults+child*/ SLOT_utime, // user+kernel jiffies
- SLOT_stime, /*kernel mode jiffies*/ SLOT_cutime, // utime+child
+ SLOT_stime, /*kernel mode jiffies*/ SLOT_cutime, // utime+child utime
SLOT_cstime, /*stime+child*/ SLOT_priority, // priority level
SLOT_nice, /*nice level*/ SLOT_numthreads,// thread count
SLOT_vmlck, /*locked memory*/ SLOT_starttime, // jiffies after boot
SLOT_policy, /*man sched_setscheduler*/SLOT_blkioticks,// IO wait time
SLOT_gtime, /*guest jiffies of task*/ SLOT_cgtime, // gtime+child
SLOT_startbss, /*data/bss address*/ SLOT_endbss, // end addr data+bss
- SLOT_upticks, /*46-19 (divisor for %)*/ SLOT_argv0len, // argv[0] length
+ SLOT_upticks, /*uptime-starttime*/ SLOT_argv0len, // argv[0] length
SLOT_uptime, /*si.uptime @read time*/ SLOT_vsz, // Virtual mem Size
SLOT_rss2, /*Resident Set Size*/ SLOT_shr, // Shared memory
SLOT_rchar, /*All bytes read*/ SLOT_wchar, // All bytes written
SLOT_tid, /*Thread ID*/ SLOT_tcount, // Thread count
SLOT_pcy, /*Android sched policy*/
- SLOT_count
+ SLOT_count /* Size of array */
};
+/* In addition to slot[], carevup contains 6 string fields to display
+ command name, tty device, selinux label... They're stored one after the
+ other in str[] (separated by null terminators), and offset[] contains the
+ starting position of each string after the first (which is always 0). */
+
// Data layout in toybuf
struct carveup {
long long slot[SLOT_count]; // data (see enum above)
- unsigned short offset[6]; // offset of fields in str[] (skip name, always 0)
+ unsigned short offset[6]; // offset of fields in str[] (skip CMD, always 0)
char state;
- char str[]; // name, tty, command, wchan, attr, cmdline
+ char str[]; // CMD, TTY, WCHAN, LABEL, COMM, ARGS, NAME
};
+/* The typos[] array lists all the types understood by "ps -o", I.E all the
+ * columns ps and top know how to display. Each entry has:
+ *
+ * name: the column name, displayed at top and used to select column with -o
+ *
+ * width: the display width. Fields are padded to this width when displaying
+ * to a terminal (negative means right justified). Strings are truncated
+ * to fit, numerical fields are padded but not truncated (although
+ * the display code reclaims unused padding from later fields to try to
+ * get the overflow back).
+ *
+ * slot: which slot[] out of carveup. Negative means it's a string field.
+ * Setting bit |64 requests extra display/sort processing.
+ *
+ * The TAGGED_ARRAY plumbing produces an enum of indexes, the "tag" is the
+ * first string argument and the prefix is the first argument to TAGGED_ARRAY
+ * so in this case "NAME" becomes PS_NAME which is the offset into typos[]
+ * for that entry, and also _PS_NAME (the bit position, 1<<PS_NAME).
+ * We record active columns in TT.bits, ala:
+ *
+ * if (TT.bits & _PS_NAME) printf("-o included PS_NAME");
+ */
+
// TODO: Android uses -30 for LABEL, but ideally it would auto-size.
// 64|slot means compare as string when sorting
struct typography {
char *name;
signed char width, slot;
} static const typos[] = TAGGED_ARRAY(PS,
- // Numbers
+ // Numbers. (What's in slot[] is what's displayed, sorted numerically.)
{"PID", 5, SLOT_pid}, {"PPID", 5, SLOT_ppid}, {"PRI", 3, SLOT_priority},
{"NI", 3, SLOT_nice}, {"ADDR", 4+sizeof(long), SLOT_eip},
{"SZ", 5, SLOT_vsize}, {"RSS", 6, SLOT_rss}, {"PGID", 5, SLOT_pgrp},
{"RTPRIO", 6, SLOT_rtprio}, {"SCH", 3, SLOT_policy}, {"CPU", 3, SLOT_taskcpu},
{"TID", 5, SLOT_tid}, {"TCNT", 4, SLOT_tcount}, {"BIT", 3, SLOT_bits},
- // String fields
+ // String fields (-1 is carveup->str, rest are str+offset[1-slot])
{"TTY", -8, -2}, {"WCHAN", -6, -3}, {"LABEL", -30, -4}, {"COMM", -27, -5},
{"NAME", -27, -7}, {"COMMAND", -27, -5}, {"CMDLINE", -27, -6},
{"ARGS", -27, -6}, {"CMD", -15, -1},
- // user/group
+ // user/group (may call getpwuid() or similar)
{"UID", 5, SLOT_uid}, {"USER", -12, 64|SLOT_uid}, {"RUID", 4, SLOT_ruid},
{"RUSER", -8, 64|SLOT_ruid}, {"GID", 8, SLOT_gid}, {"GROUP", -8, 64|SLOT_gid},
{"RGID", 4, SLOT_rgid}, {"RGROUP", -8, 64|SLOT_rgid},
- // clock displays
+ // clock displays (00:00:00)
{"TIME", 8, SLOT_utime}, {"ELAPSED", 11, SLOT_starttime},
{"TIME+", 9, SLOT_utime},
- // Percentage displays
+ // Percentage displays (fixed point, one decimal digit. 123 -> 12.3)
{"C", 1, SLOT_utime2}, {"%VSZ", 5, SLOT_vsize}, {"%MEM", 5, SLOT_rss},
{"%CPU", 4, SLOT_utime2},
- // human_readable
+ // human_readable (function human_readable() in lib, 1.23M, 1.4G, etc)
{"VIRT", 4, SLOT_vsz}, {"RES", 4, SLOT_rss2},
{"SHR", 4, SLOT_shr}, {"READ", 6, SLOT_rchar}, {"WRITE", 6, SLOT_wchar},
{"IO", 6, SLOT_iobytes}, {"DREAD", 6, SLOT_rbytes},
{"DWRITE", 6, SLOT_wbytes}, {"SWAP", 6, SLOT_swap}, {"DIO", 6, SLOT_diobytes},
- // Misc
+ // Misc (special cases)
{"STIME", 5, SLOT_starttime}, {"F", 1, 64|SLOT_flags}, {"S", -1, 64},
{"STAT", -5, 64}, {"PCY", 3, 64|SLOT_pcy},
);
// Output the field, appropriately padded
// Minimum one space between each field
+ if (width<2) break;
if (field != TT.fields) {
putchar(' ');
width--;
memset(slot, 0, sizeof(tb->slot));
tb->slot[SLOT_tid] = *slot = atol(new->name);
- if (TT.threadparent && TT.threadparent->extra)
- if (*slot == *(((struct carveup *)TT.threadparent->extra)->slot)) return 0;
+ if (TT.threadparent && TT.threadparent->extra) {
+ *slot = *(((struct carveup *)TT.threadparent->extra)->slot);
+ // Parent also shows up as a thread, discard duplicate
+ if (*slot == tb->slot[SLOT_tid]) return 0;
+ }
fd = dirtree_parentfd(new);
len = 2048;
// Save or display
if (!TT.show_process) return DIRTREE_SAVE;
TT.show_process((void *)new->extra);
- dt = new->child;
- new->child = 0;
- while (dt->child) {
- new = dt->child->next;
- TT.show_process((void *)dt->child->extra);
- free(dt->child);
- dt->child = new;
+ if ((dt = new->child)) {
+ new->child = 0;
+ while (dt->child) {
+ new = dt->child->next;
+ TT.show_process((void *)dt->child->extra);
+ free(dt->child);
+ dt->child = new;
+ }
+ free(dt);
}
- free(dt);
return 0;
}
comma_args(arg ? arg : &def, fields, err, parse_ko);
}
-static void shared_main(void)
+void ps_main(void)
{
+ char **arg;
+ struct dirtree *dt;
+ char *not_o;
int i;
- TT.ticks = sysconf(_SC_CLK_TCK);
- if (!TT.width) {
- TT.width = 80;
- TT.height = 25;
- // If ps can't query terminal size pad to 80 but do -w
- if (toys.which->name[1] == 's') {
- if (!isatty(1) || !terminal_size(&TT.width, &TT.height))
- toys.optflags |= FLAG_w;
- }
- }
+ TT.ticks = sysconf(_SC_CLK_TCK); // units for starttime/uptime
- // find controlling tty, falling back to /dev/tty if none
- for (i = 0; !TT.tty && i<4; i++) {
+ if (-1 != (i = tty_fd())) {
struct stat st;
- int fd = i;
- if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break;
-
- if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev;
- if (i==3) close(fd);
+ if (!fstat(i, &st)) TT.tty = st.st_rdev;
}
-}
-
-void ps_main(void)
-{
- char **arg;
- struct dirtree *dt;
- char *not_o;
- int i;
- shared_main();
+ // If we can't query terminal size pad to 80 but do -w
+ TT.width = 80;
+ if (!isatty(1) || !terminal_size(&TT.width, 0))
+ toys.optflags |= FLAG_w;
if (toys.optflags&FLAG_w) TT.width = 99999;
// parse command line options other than -o
return line-1;
}
-static long long millitime(void)
-{
- struct timespec ts;
-
- clock_gettime(CLOCK_MONOTONIC, &ts);
- return ts.tv_sec*1000+ts.tv_nsec/1000000;
-}
-
static void top_common(
int (*filter)(long long *oslot, long long *nslot, int milis))
{
terminal_probesize(&TT.width, &TT.height);
}
}
+ if (TT.top.m) TT.height = TT.top.m+5;
lines = TT.height;
}
if (recalc && !(toys.optflags&FLAG_q)) {
static void top_setup(char *defo, char *defk)
{
TT.top.d *= 1000;
+
+ TT.ticks = sysconf(_SC_CLK_TCK); // units for starttime/uptime
+ TT.tty = tty_fd() != -1;
+
+ // Are we doing "batch" output or interactive?
if (toys.optflags&FLAG_b) TT.width = TT.height = 99999;
else {
+ // Grab starting time, make terminal raw, switch off cursor,
+ // set signal handler to put terminal/cursor back to normal at exit.
TT.time = millitime();
set_terminal(0, 1, 0);
sigatexit(tty_sigreset);
xsignal(SIGWINCH, generic_signal);
printf("\033[?25l\033[0m");
+ TT.width = 80;
+ TT.height = 25;
}
- 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.pgrep.self = getpid();
- // No signal names start with "L", so no need for "L: " parsing.
+ // No signal names start with "L", so no need for "L: " in optstr.
if (TT.pgrep.L && 1>(TT.pgrep.signal = sig_to_num(TT.pgrep.L)))
error_exit("bad -L '%s'", TT.pgrep.L);