aboutsummaryrefslogtreecommitdiff
path: root/toys/posix/ps.c
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2018-07-18 20:00:03 -0500
committerRob Landley <rob@landley.net>2018-07-18 20:00:03 -0500
commit98be9501e6cadde4fd5dd3227fad9ea47609167b (patch)
tree7a49df57d46946d46ab18014afb28a474adef8ce /toys/posix/ps.c
parent05cca08e0ffe15d170d4f8d251be4257dd432617 (diff)
downloadtoybox-98be9501e6cadde4fd5dd3227fad9ea47609167b.tar.gz
Factor out "ps -o help" to show fields, move help text into -o type array,
add comments to get_ps().
Diffstat (limited to 'toys/posix/ps.c')
-rw-r--r--toys/posix/ps.c258
1 files changed, 174 insertions, 84 deletions
diff --git a/toys/posix/ps.c b/toys/posix/ps.c
index bb491eba..8a2c487f 100644
--- a/toys/posix/ps.c
+++ b/toys/posix/ps.c
@@ -76,7 +76,7 @@ config PS
-k Sort FIELDs (-FIELD to reverse) -M Measure/pad future field widths
-n Show numeric USER and GROUP -w Wide output (don't truncate fields)
- Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD)
+ Which FIELDs to show. (-o HELP for list, default = -o PID,TTY,TIME,CMD)
-f Full listing (-o USER:12=UID,PID,PPID,C,STIME,TTY,TIME,ARGS=CMD)
-l Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD)
@@ -84,42 +84,6 @@ config PS
-O Add FIELDS to defaults
-Z Include LABEL
- Command line -o fields:
-
- ARGS CMDLINE minus initial path CMD Command (thread) name (stat[2])
- CMDLINE Command line (argv[]) COMM Command filename (/proc/$PID/exe)
- COMMAND Command file (/proc/$PID/exe) NAME Process name (argv[0] of $PID)
-
- Process attribute -o FIELDs:
-
- ADDR Instruction pointer BIT Is this process 32 or 64 bits
- CPU Which processor running on ETIME Elapsed time since PID start
- F Flags (1=FORKNOEXEC 4=SUPERPRIV) GID Group id
- GROUP Group name LABEL Security label
- MAJFL Major page faults MINFL Minor page faults
- NI Niceness (lower is faster)
- PCPU Percentage of CPU time used PCY Android scheduling policy
- PGID Process Group ID
- PID Process ID PPID Parent Process ID
- PRI Priority (higher is faster) PSR Processor last executed on
- RGID Real (before sgid) group ID RGROUP Real (before sgid) group name
- RSS Resident Set Size (pages in use) RTPRIO Realtime priority
- RUID Real (before suid) user ID RUSER Real (before suid) user name
- S Process state:
- R (running) S (sleeping) D (device I/O) T (stopped) t (traced)
- Z (zombie) X (deader) x (dead) K (wakekill) W (waking)
- SCHED Scheduling policy (0=other, 1=fifo, 2=rr, 3=batch, 4=iso, 5=idle)
- STAT Process state (S) plus:
- < high priority N low priority L locked memory
- s session leader + foreground l multithreaded
- STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss)
- SZ Memory Size (4k pages needed to completely swap out process)
- TCNT Thread count TID Thread ID
- TIME CPU time consumed TTY Controlling terminal
- UID User id USER User name
- VSZ Virtual memory size (1k units) %VSZ VSZ as % of physical memory
- WCHAN Wait location in kernel
-
config TOP
bool "top"
default y
@@ -323,7 +287,7 @@ enum {
SLOT_startbss, /*data/bss address*/ SLOT_endbss, // end addr data+bss
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_rss2, /*rss from "statm"*/ SLOT_shr, // Shared memory
SLOT_rchar, /*All bytes read*/ SLOT_wchar, // All bytes written
SLOT_rbytes, /*Disk bytes read*/ SLOT_wbytes, // Disk bytes written
SLOT_swap, /*Swap pages used*/ SLOT_bits, // 32 or 64
@@ -372,47 +336,149 @@ struct procpid {
// TODO: Android uses -30 for LABEL, but ideally it would auto-size.
// 64|slot means compare as string when sorting
struct typography {
- char *name;
+ char *name, *help;
signed char width, slot;
} static const typos[] = TAGGED_ARRAY(PS,
// 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},
- {"VSZ", 7, SLOT_vsize}, {"MAJFL", 6, SLOT_majflt}, {"MINFL", 6, SLOT_minflt},
- {"PR", 2, SLOT_priority}, {"PSR", 3, SLOT_taskcpu},
- {"RTPRIO", 6, SLOT_rtprio}, {"SCH", 3, SLOT_policy}, {"CPU", 3, SLOT_taskcpu},
- {"TID", 5, SLOT_tid}, {"TCNT", 4, SLOT_tcount}, {"BIT", 3, SLOT_bits},
+ {"PID", "Process ID", 5, SLOT_pid},
+ {"PPID", "Parent Process ID", 5, SLOT_ppid},
+ {"PRI", "Priority (dynamic 0 to 139)", 3, SLOT_priority},
+ {"NI", "Niceness (static 19 to -20)", 3, SLOT_nice},
+ {"ADDR", "Instruction pointer", 4+sizeof(long), SLOT_eip},
+ {"SZ", "4k pages to swap out", 5, SLOT_vsize},
+ {"RSS", "Resident Set Size (DRAM pages)", 6, SLOT_rss},
+ {"PGID", "Process Group ID", 5, SLOT_pgrp},
+ {"VSZ", "Virtual memory size (1k units)", 7, SLOT_vsize},
+ {"MAJFL", "Major page faults", 6, SLOT_majflt},
+ {"MINFL", "Minor page faults", 6, SLOT_minflt},
+ {"PR", "Prio Reversed (dyn 39-0, RT)", 2, SLOT_priority},
+ {"PSR", "Processor last executed on", 3, SLOT_taskcpu},
+ {"RTPRIO", "Realtime priority", 6, SLOT_rtprio},
+ {"SCH", "Scheduling policy (0=other, 1=fifo, 2=rr, 3=batch, 4=iso, 5=idle)",
+ 3, SLOT_policy},
+ {"CPU", "Which processor running on", 3, SLOT_taskcpu},
+ {"TID", "Thread ID", 5, SLOT_tid},
+ {"TCNT", "Thread count", 4, SLOT_tcount},
+ {"BIT", "32 or 64", 3, SLOT_bits},
// String fields (-1 is procpid->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},
+ {"TTY", "Controlling terminal", -8, -2},
+ {"WCHAN", "Wait location in kernel", -6, -3},
+ {"LABEL", "Security label", -30, -4},
+ {"COMM", "EXE filename (/proc/PID/exe)", -27, -5},
+ {"NAME", "Process name (PID's argv[0])", -27, -7},
+ {"COMMAND", "EXE path (/proc/PID/exe)", -27, -5},
+ {"CMDLINE", "Command line (argv[])", -27, -6},
+ {"ARGS", "CMDLINE minus initial path", -27, -6},
+ {"CMD", "Thread name (/proc/TID/stat:2)", -15, -1},
// 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},
+ {"UID", "User id", 5, SLOT_uid},
+ {"USER", "User name", -12, 64|SLOT_uid},
+ {"RUID", "Real (before suid) user ID", 4, SLOT_ruid},
+ {"RUSER", "Real (before suid) user name", -8, 64|SLOT_ruid},
+ {"GID", "Group ID", 8, SLOT_gid},
+ {"GROUP", "Group name", -8, 64|SLOT_gid},
+ {"RGID", "Real (before sgid) Group ID", 4, SLOT_rgid},
+ {"RGROUP", "Real (before sgid) group name", -8, 64|SLOT_rgid},
// clock displays (00:00:00)
- {"TIME", 8, SLOT_utime}, {"ELAPSED", 11, SLOT_starttime},
- {"TIME+", 9, SLOT_utime},
+ {"TIME", "CPU time consumed", 8, SLOT_utime},
+ {"ELAPSED", "Elapsed time since PID start", 11, SLOT_starttime},
+ {"TIME+", "CPU time (high precision)", 9, SLOT_utime},
// 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},
+ {"C", "Total %CPU used since start", 1, SLOT_utime2},
+ {"%VSZ", "VSZ as % of physical memory", 5, SLOT_vsize},
+ {"%MEM", "RSS as % of physical memory", 5, SLOT_rss},
+ {"%CPU", "Percentage of CPU time used", 4, SLOT_utime2},
// 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},
+ {"VIRT", "Virtual memory size", 4, SLOT_vsz},
+ {"RES", "Short RSS", 4, SLOT_rss2},
+ {"SHR", "Shared memory", 4, SLOT_shr},
+ {"READ", "Data read", 6, SLOT_rchar},
+ {"WRITE", "Data written", 6, SLOT_wchar},
+ {"IO", "Data I/O", 6, SLOT_iobytes},
+ {"DREAD", "Pirate Roberts", 6, SLOT_rbytes},
+ {"DWRITE", "Disk write", 6, SLOT_wbytes},
+ {"SWAP", "Swap I/O", 6, SLOT_swap},
+ {"DIO", "Disk I/O", 6, SLOT_diobytes},
// Misc (special cases)
- {"STIME", 5, SLOT_starttime}, {"F", 1, 64|SLOT_flags}, {"S", -1, 64},
- {"STAT", -5, 64}, {"PCY", 3, 64|SLOT_pcy},
+ {"STIME", "Start time (ISO 8601)", 5, SLOT_starttime},
+ {"F", "Flags 1=FORKNOEXEC 4=SUPERPRIV", 1, 64|SLOT_flags},
+ {"S", "Process state:\n"
+ "\t R (running) S (sleeping) D (device I/O) T (stopped) t (traced)\n"
+ "\t Z (zombie) X (deader) x (dead) K (wakekill) W (waking)",
+ -1, 64},
+ {"STAT", "Process state (S) plus:\n"
+ "\t < high priority N low priority L locked memory\n"
+ "\t s session leader + foreground l multithreaded",
+ -5, 64},
+ {"PCY", "Android scheduling policy", 3, 64|SLOT_pcy},
);
+// Show "-o help" text
+
+void inherent_in_the_system(int len, int multi)
+{
+ int i, j, k, left = 0;
+ struct typography *t;
+
+ // Quick and dirty sort of toybuf[] entries (see TODO below)
+ for (j = len; j--; ) {
+ k = -1;
+
+ for (i=0; i<j; i++) {
+ if (strcmp(typos[toybuf[i]].name, typos[toybuf[i+1]].name)>0) {
+ k = toybuf[i];
+ toybuf[i] = toybuf[i+1];
+ toybuf[i+1] = k;
+ }
+ }
+ if (k == -1) break;
+ }
+
+ // Display loop
+ for (i = j = 0; i<len; i++, j++) {
+ t = (void *)(typos+toybuf[i]);
+ if (strlen(t->help)>30) {
+ if (multi) printf(" %-8s%s\n", t->name, t->help);
+ else j--;
+ } else if (!multi) {
+ left = !(j&1);
+ printf(" %-8s%*s%c"+2*!left, t->name, -30*left, t->help, 10+22*left);
+ }
+ }
+ if (!multi && left) xputc('\n');
+}
+
+// Print help text for unknown -o field.
+void being_repressed(void)
+{
+ int i, jump = PS_CMD+1-PS_COMM;
+
+ // TODO: sort the array of -o types so they're already alphabetical and
+ // don't need sorting here. A regex to find everything that currently cares
+ // about symbol order might be: "which *[><]=* *PS"
+
+ // Collate variants of command line display.
+
+ printf("Command line -o fields:\n\n");
+ for (i = 0; i<jump; i++) toybuf[i] = PS_COMM+i;
+ inherent_in_the_system(jump, 0);
+
+ // Show the rest of the commands.
+
+ printf("\nProcess attribute -o FIELDs:\n\n");
+ for (i = 0; i<ARRAY_LEN(typos)-jump; i++) toybuf[i] = i+(i>=PS_COMM)*jump;
+ inherent_in_the_system(ARRAY_LEN(typos)-jump, 1);
+ inherent_in_the_system(ARRAY_LEN(typos)-jump, 0);
+
+ xexit();
+}
+
// Return 0 to discard, nonzero to keep
static int shared_match_process(long long *slot)
{
@@ -646,9 +712,9 @@ static void show_ps(void *p)
putchar(TT.time ? '\r' : '\n');
}
-// dirtree callback: read data about process to display, store, or discard it.
+// dirtree callback: read data about process, then display or store it.
// Fills toybuf with struct procpid and either DIRTREE_SAVEs a copy to ->extra
-// (in -k mode) or calls show_ps on toybuf (no malloc/copy/free there).
+// (in -k mode) or calls show_ps directly on toybuf (for low memory systems).
static int get_ps(struct dirtree *new)
{
struct {
@@ -671,6 +737,7 @@ static int get_ps(struct dirtree *new)
return DIRTREE_RECURSE|DIRTREE_SHUTUP|DIRTREE_PROC
|(DIRTREE_SAVE*(TT.threadparent||!TT.show_process));
+ // Grab PID and figure out if we're a thread or a process
memset(slot, 0, sizeof(tb->slot));
slot[SLOT_tid] = *slot = atol(new->name);
if (TT.threadparent && TT.threadparent->extra) {
@@ -680,20 +747,25 @@ static int get_ps(struct dirtree *new)
}
fd = dirtree_parentfd(new);
+ // Read /proc/$PID/stat into half of toybuf.
len = 2048;
sprintf(buf, "%lld/stat", slot[SLOT_tid]);
if (!readfileat(fd, buf, buf, &len)) return 0;
- // parse oddball fields (name and state). Name can have embedded ')' so match
- // _last_ ')' in stat (although VFS limits filenames to 255 bytes max).
- // All remaining fields should be numeric.
+ // parse oddball fields: the first field is same as new->name (skip it)
+ // and the second and third (name and state) are the only non-numeric fields.
+ // Name has (parentheses) around it, and can have embedded ')' so match
+ // _last_ ')' (VFS limits filenames to 255 bytes max, sanity check that).
+ // TODO: kernel task struct actually limits name to 16 chars?
if (!(name = strchr(buf, '('))) return 0;
for (s = ++name; *s; s++) if (*s == ')') end = s;
if (!end || end-name>255) return 0;
-
- // Parse numeric fields (starting at 4th field in slot[SLOT_ppid])
if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0;
- for (j = 1; j<SLOT_count; j++)
+
+ // All remaining fields should be numeric, parse them into slot[] array
+ // (skipping first 3 stat fields and first slot[], both were handled above)
+ // yes this means the alignment's off: stat[4] becomes slot[1]
+ for (j = SLOT_ppid; j<SLOT_count; j++)
if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
// Now we've read the data, move status and name right after slot[] array,
@@ -705,6 +777,8 @@ static int get_ps(struct dirtree *new)
*buf++ = 0;
len = sizeof(toybuf)-(buf-toybuf);
+ // Overwrite useless/obsolete stat fields with more interesting data.
+
// save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
// or numeric wchan, and the remaining two are always zero), and vmlck into
// 18 (which is "obsolete, always 0" from stat)
@@ -715,8 +789,7 @@ static int get_ps(struct dirtree *new)
slot[SLOT_utime] += slot[SLOT_stime];
slot[SLOT_utime2] = slot[SLOT_utime];
- // If RGROUP RUSER STAT RUID RGID SWAP happening, or -G or -U, parse "status"
- // and save ruid, rgid, and vmlck.
+ // Do we need to read "status"?
if ((TT.bits&(_PS_RGROUP|_PS_RUSER|_PS_STAT|_PS_RUID|_PS_RGID|_PS_SWAP
|_PS_IO|_PS_DIO)) || TT.GG.len || TT.UU.len)
{
@@ -782,10 +855,18 @@ static int get_ps(struct dirtree *new)
if (TT.bits&_PS_PCY)
get_sched_policy(slot[SLOT_tid], (void *)&slot[SLOT_pcy]);
+ // Done using buf[] (tb->str) as scratch space, now read string data,
+ // saving consective null terminated strings. (Save starting offsets into
+ // str->offset to avoid strlen() loop to find relevant string.)
+
// Fetch string data while parentfd still available, appending to buf.
// (There's well over 3k of toybuf left. We could dynamically malloc, but
// it'd almost never get used, querying length of a proc file is awkward,
// fixed buffer is nommu friendly... Wait for somebody to complain. :)
+
+ // The fetch[] array at the start of the function says what file to read
+ // and what -o display field outputs it (to skip the ones we don't need).
+
slot[SLOT_argv0len] = 0;
for (j = 0; j<ARRAY_LEN(fetch); j++) {
tb->offset[j] = buf-(tb->str);
@@ -794,12 +875,17 @@ static int get_ps(struct dirtree *new)
continue;
}
- // Determine remaining space, reserving minimum of 256 bytes/field and
- // 260 bytes scratch space at the end (for output conversion later).
- len = sizeof(toybuf)-(buf-toybuf)-260-256*(ARRAY_LEN(fetch)-j);
+ // Determine available space: reserve 256 bytes (guaranteed minimum) for
+ // each string we haven't checked yet, tb->str starts after the numeric
+ // arrays in struct procpid, and we reserve 260 bytes scratch space at the
+ // end of toybuf for output conversion in string_field(). Other than that,
+ // each use all available space, and future strings that don't use their
+ // guaranteed minimum add to the pool.
+ len = sizeof(toybuf)-256*(ARRAY_LEN(fetch)-j)-(buf-toybuf)-260;
sprintf(buf, "%lld/%s", slot[SLOT_tid], fetch[j].name);
- // For exe we readlink instead of read contents
+ // For exe (j==3) readlink() instead of reading file's contents
+ // for -o NAME (j==5) copy data from threadparent (PID) into thread (TID).
if (j==3 || j==5) {
struct procpid *ptb = 0;
int k;
@@ -825,8 +911,7 @@ static int get_ps(struct dirtree *new)
buf[len] = 0;
}
- // If it's not the TTY field, data we want is in a file.
- // Last length saved in slot[] is command line (which has embedded NULs)
+ // Turning stat's SLOT_ttynr into a string is an outright heuristic ordeal.
} else if (!j) {
int rdev = slot[SLOT_ttynr];
struct stat st;
@@ -869,8 +954,7 @@ static int get_ps(struct dirtree *new)
if (strstart(&s, "/dev/")) memmove(buf, s, len -= 4);
}
- // Data we want is in a file.
- // Last length saved in slot[] is command line (which has embedded NULs)
+ // For the rest, the data we want is in a file we can just read.
} else {
int temp = 0;
@@ -882,7 +966,7 @@ static int get_ps(struct dirtree *new)
if (!buf[len-1] || isspace(buf[len-1])) buf[--len] = 0;
else break;
- // Turn NUL to space, other low ascii to ? (in non-tty mode)
+ // Turn NUL to space, other low ascii to ? (in non-tty mode), except
// cmdline has a trailing NUL that we don't want to turn to space.
for (i=0; i<len-1; i++) {
char c = buf[i];
@@ -900,10 +984,11 @@ static int get_ps(struct dirtree *new)
slot[SLOT_argv0len] = temp ? temp : len; // Position of _first_ NUL
}
- // Above calculated/retained len, so we don't need to re-strlen.
+ // Each case above calculated/retained len, so we don't need to re-strlen.
buf += len+1;
}
+ // Record that we saw another process, and display/return now if appropriate
TT.kcount++;
if (TT.show_process && !TT.threadparent) {
TT.show_process(tb);
@@ -911,7 +996,7 @@ static int get_ps(struct dirtree *new)
return 0;
}
- // If we need to sort the output, add it to the list and return.
+ // We're retaining data (probably to sort it), save copy in list.
s = xmalloc(buf-toybuf);
new->extra = (long)s;
memcpy(s, toybuf, buf-toybuf);
@@ -976,6 +1061,9 @@ static char *parse_ko(void *data, char *type, int length)
char *width, *title, *end, *s;
int i, j, k;
+ // Caller's WOULD_EXIT catches -o help and prints help
+ if (length==4 && !strncasecmp(type, "HELP", length)) xexit();
+
// Get title, length of title, type, end of type, and display width
// Chip off =name to display
@@ -1180,10 +1268,12 @@ static struct procpid **collate(int count, struct dirtree *dt)
static void default_ko(char *s, void *fields, char *err, struct arg_list *arg)
{
struct arg_list def;
+ int x;
memset(&def, 0, sizeof(struct arg_list));
def.arg = s;
- comma_args(arg ? arg : &def, fields, err, parse_ko);
+ WOULD_EXIT(x, comma_args(arg ? arg : &def, fields, err, parse_ko));
+ if (x) being_repressed();
}
void ps_main(void)