top: add a flexible 'Inspect' capability

This commit introduces an extremely powerful, flexible
brand new capability.  Now, users can pause the normal
iterative display and inspect the contents of any file
or output from any script, command, or even pipelines.

It's invoked via the 'Y' interactive command which, in
turn, is supported with simple user supplied additions
as new entries in the top personal configuration file.

A separate new 'Inspect' window supports scrolling and
searching, similar to the main top display.  Except it
extends existing 'L'/'&' (locate/locate-next) commands
so that an out-of-view match automatically adjusts the
horizontal position bringing such data into view.  And
it provides for multiple successive same line matches.

Also, the basic 'more/less' navigation keys are active
in this new 'Inspect' window, to ease user transition.

There are no program changes required when entries are
added to or deleted from the rcfile.  And there are no
known limits to the complexity of a script, command or
pipeline, other than the unidirectional nature imposed
by the 'popen' function call which top cannot violate.

Since it's impossible to predict exactly what contents
will be generated, top treats all output as raw binary
data.  Any control characters display in '^C' notation
while all other unprintable characters show as '<AB>'.

The biggest problem encountered was with the find/next
capability since that strstr guy was really diminished
given the possibility that numerous 'strings' could be
encountered *within* many of top's raw, binary 'rows'.

Oh, and another problem was in maintaining the perfect
left & right text justification of this commit message
along with all of the commit summaries.  Some of those
summaries (like this very one) are of course, slightly
shorter, to make room for the 'man document' addition.

Enjoy!

Signed-off-by: Jim Warner <james.warner@comcast.net>
This commit is contained in:
Jim Warner 2012-11-25 00:00:05 -05:00 committed by Craig Small
parent 9e99c84c48
commit 081fe506f3
4 changed files with 668 additions and 18 deletions

578
top/top.c
View File

@ -597,7 +597,7 @@ static void capsmk (WIN_t *q) {
if (!capsdone) { if (!capsdone) {
STRLCPY(Cap_clr_eol, tIF(clr_eol)) STRLCPY(Cap_clr_eol, tIF(clr_eol))
STRLCPY(Cap_clr_scr, tIF(clear_screen)) STRLCPY(Cap_clr_scr, tIF(clear_screen))
// due to leading newline, only 1 function may use this (and carefully) // due to the leading newline, the following must be used with care
snprintf(Cap_nl_clreos, sizeof(Cap_nl_clreos), "\n%s", tIF(clr_eos)); snprintf(Cap_nl_clreos, sizeof(Cap_nl_clreos), "\n%s", tIF(clr_eos));
STRLCPY(Cap_curs_huge, tIF(cursor_visible)) STRLCPY(Cap_curs_huge, tIF(cursor_visible))
STRLCPY(Cap_curs_norm, tIF(cursor_normal)) STRLCPY(Cap_curs_norm, tIF(cursor_normal))
@ -739,18 +739,24 @@ static void show_special (int interact, const char *glob) {
| capclr_rowhigh, = \007, | | capclr_rowhigh, = \007, |
| capclr_rownorm }; = \010 [octal!] | | capclr_rownorm }; = \010 [octal!] |
+------------------------------------------------------+ */ +------------------------------------------------------+ */
/* ( pssst, after adding the termcap transitions, row may ) /* ( Pssst, after adding the termcap transitions, row may )
( exceed 300+ bytes, even in an 80x24 terminal window! ) */ ( exceed 300+ bytes, even in an 80x24 terminal window! )
char tmp[SMLBUFSIZ], lin[MEDBUFSIZ], row[LRGBUFSIZ]; ( And if we're no longer guaranteed lines created only )
( by top, we'll need larger buffs plus some protection )
( against overrunning them with this 'lin_end - glob'. ) */
char tmp[LRGBUFSIZ], lin[LRGBUFSIZ], row[ROWMAXSIZ];
char *rp, *lin_end, *sub_beg, *sub_end; char *rp, *lin_end, *sub_beg, *sub_end;
int room; int room;
// handle multiple lines passed in a bunch // handle multiple lines passed in a bunch
while ((lin_end = strchr(glob, '\n'))) { while ((lin_end = strchr(glob, '\n'))) {
#define myMIN(a,b) (((a) < (b)) ? (a) : (b))
size_t lessor = myMIN((size_t)(lin_end - glob), sizeof(lin) -1);
// create a local copy we can extend and otherwise abuse // create a local copy we can extend and otherwise abuse
memcpy(lin, glob, (unsigned)(lin_end - glob)); memcpy(lin, glob, lessor);
// zero terminate this part and prepare to parse substrings // zero terminate this part and prepare to parse substrings
lin[lin_end - glob] = '\0'; lin[lessor] = '\0';
room = Screen_cols; room = Screen_cols;
sub_beg = sub_end = lin; sub_beg = sub_end = lin;
*(rp = row) = '\0'; *(rp = row) = '\0';
@ -765,7 +771,8 @@ static void show_special (int interact, const char *glob) {
case 1: case 2: case 3: case 4: case 1: case 2: case 3: case 4:
case 5: case 6: case 7: case 8: case 5: case 6: case 7: case 8:
*sub_end = '\0'; *sub_end = '\0';
snprintf(tmp, sizeof(tmp), "%s%.*s%s", Curwin->captab[ch], room, sub_beg, Caps_off); snprintf(tmp, sizeof(tmp), "%s%.*s%s"
, Curwin->captab[ch], room, sub_beg, Caps_off);
rp = scat(rp, tmp); rp = scat(rp, tmp);
room -= (sub_end - sub_beg); room -= (sub_end - sub_beg);
sub_beg = (sub_end += 2); sub_beg = (sub_end += 2);
@ -780,6 +787,7 @@ static void show_special (int interact, const char *glob) {
else PUFF("%s%s\n", row, Caps_endline); else PUFF("%s%s\n", row, Caps_endline);
glob = ++lin_end; // point to next line (maybe) glob = ++lin_end; // point to next line (maybe)
#undef myMIN
} // end: while 'lines' } // end: while 'lines'
/* If there's anything left in the glob (by virtue of no trailing '\n'), /* If there's anything left in the glob (by virtue of no trailing '\n'),
@ -817,7 +825,7 @@ static void updt_scroll_msg (void) {
, "%%s%s %.*s%s", Caps_off, Screen_cols - 3, tmp2, Cap_clr_eol); , "%%s%s %.*s%s", Caps_off, Screen_cols - 3, tmp2, Cap_clr_eol);
} // end: updt_scroll_msg } // end: updt_scroll_msg
/*###### Low Level Memory/Keyboard support #############################*/ /*###### Low Level Memory/Keyboard/File I/O support ####################*/
/* /*
* Handle our own memory stuff without the risk of leaving the * Handle our own memory stuff without the risk of leaving the
@ -1048,6 +1056,37 @@ static char *linein (const char *prompt) {
#undef bufMAX #undef bufMAX
} // end: linein } // end: linein
#endif #endif
/*
* This routine provides the i/o in support of files whose size
* cannot be determined in advance. Given a stream pointer, he'll
* try to slurp in the whole thing and return a dynamically acquired
* buffer supporting that single string glob.
*
* He always creates a buffer at least READMINSZ big, possibly
* all zeros (an empty string), even if the file wasn't read. */
static int readfile (FILE *fp, char **baddr, unsigned *bsize, unsigned *bread) {
char chunk[4096*16];
size_t num;
*bread = 0;
*bsize = READMINSZ;
*baddr = alloc_c(READMINSZ);
if (fp) {
while (0 < (num = fread(chunk, 1, sizeof(chunk) -1, fp))) {
if (feof(fp) && chunk[num -1]) chunk[num++] = '\0';
*baddr = alloc_r(*baddr, num + *bsize);
memcpy(*baddr + *bread, chunk, num);
*bread += num;
*bsize += num;
};
// adjust for the null terminator, which was counted above
if (*bread) --(*bread);
return ferror(fp);
}
return ENOENT;
} // end: readfile
/*###### Small Utility routines ########################################*/ /*###### Small Utility routines ########################################*/
@ -2265,6 +2304,445 @@ static void sysinfo_refresh (int forced) {
#endif #endif
} // end: sysinfo_refresh } // end: sysinfo_refresh
/*###### Inspect Other Output ##########################################*/
/*
* HOWTO Extend the top 'inspect' functionality:
*
* To exploit the 'Y' interactive command, one must add entries to
* the top personal configuration file. Such entries simply reflect
* a file to be read or command/pipeline to be executed whose results
* will then be displayed in a separate scrollable window.
*
* Entries beginning with a '#' character are ignored, regardless of
* content. Otherwise they consist of the following 3 elements, each
* of which must be separated by a tab character (thus 2 '\t' total):
* type: literal 'file' or 'pipe'
* name: selection shown on the Inspect screen
* fmts: string representing a path or command
*
* The two types of Inspect entries are not interchangeable.
* Those designated 'file' will be accessed using fopen/fread and must
* reference a single file in the 'fmts' element. Entries specifying
* 'pipe' will employ popen/fread, their 'fmts' element could contain
* many pipelined commands and, none can be interactive.
*
* Here are some examples of both types of inspection entries.
* The first entry will be ignored due to the initial '#' character.
* For clarity, the pseudo tab depictions (^I) are surrounded by an
* extra space but the actual tabs would not be.
*
* # pipe ^I Sockets ^I lsof -n -P -i 2>&1
* pipe ^I Open Files ^I lsof -P -p %d 2>&1
* file ^I NUMA Info ^I /proc/%d/numa_maps
* pipe ^I Log ^I tail -n100 /var/log/syslog | sort -Mr
*
* Caution: If the output contains unprintable characters they will
* be displayed in either the ^I notation or hexidecimal <FF> form.
* This applies to tab characters as well. So if one wants a more
* accurate display, any tabs should be expanded within the 'fmts'.
*
* The following example takes what could have been a 'file' entry
* but employs a 'pipe' instead so as to expand the tabs.
*
* # next would have contained '\t' ...
* # file ^I <your_name> ^I /proc/%d/status
* # but this will eliminate embedded '\t' ...
* pipe ^I <your_name> ^I cat /proc/%d/status | expand -
*/
static char **Insp_p; // pointers to each line start
static int Insp_nl; // total lines, total Insp_p entries
static char *Insp_buf; // the results from insp_do_file/pipe
static unsigned Insp_bufsz; // allocated size of Insp_buf
static unsigned Insp_bufrd; // bytes actually in Insp_buf
static char *Insp_selname; // the selected label, if anybody cares
static char *Insp_selfmts; // the selected path/command, ditto
// Our 'make status line' macro
#define INSP_MKSL(big,txt) { int _sz = big ? Screen_cols : 80; \
putp(tg2(0, (Msg_row = 3))); \
PUTT("%s%.*s", Curwin->capclr_hdr, Screen_cols \
, fmtmk("%-*.*s%s", _sz, _sz, txt, Cap_clr_eol)); \
putp(Caps_off); }
// Our 'row length' macro, equivalent to a strlen() call
#define INSP_RLEN(idx) (size_t)(Insp_p[idx +1] - Insp_p[idx] -1)
// Our 'busy' (wait please) macro
#define INSP_BUSY { INSP_MKSL(0, N_txt(YINSP_workin_txt)); \
fflush(stdout); }
/*
* Establish the number of lines present in the Insp_buf glob plus
* build the all important row start array. It is that array that
* others will rely on since we dare not try to use strlen() on what
* is potentially raw binary data. Who knows what some user might
* name as a file or include in a pipeline (scary, ain't it?). */
static void insp_cnt_nl (void) {
char *beg = Insp_buf;
char *cur = Insp_buf;
char *end = Insp_buf + Insp_bufrd + 1;
#ifdef INSP_SAVEBUF
{
static int n = 1;
char fn[SMLBUFSIZ];
FILE *fd;
snprintf(fn, sizeof(fn), "%s.Insp_buf.%02d.txt", Myname, n++);
fd = fopen(fn, "w");
if (fd) {
fwrite(Insp_buf, 1, Insp_bufrd, fd);
fclose(fd);
}
}
#endif
Insp_p = alloc_c(sizeof(char*) * 2);
for (Insp_nl = 0; beg < end; beg++) {
if (*beg == '\n') {
Insp_p[Insp_nl++] = cur;
// keep our array ahead of next potential need (plus the 2 above)
Insp_p = alloc_r(Insp_p, (sizeof(char*) * (Insp_nl +3)));
cur = beg +1;
}
}
Insp_p[0] = Insp_buf;
Insp_p[Insp_nl++] = cur;
Insp_p[Insp_nl] = end;
if ((end - cur) == 1) // if there's a eof null delimiter,
--Insp_nl; // don't count it as a new line
} // end: insp_cnt_nl
#ifndef INSP_OFFDEMO
/*
* The pseudo output DEMO utility. */
static void insp_do_demo (char *fmts, int pid) {
(void)fmts; (void)pid;
Insp_bufsz = READMINSZ;
Insp_buf = alloc_c(Insp_bufsz);
Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s", N_txt(YINSP_demo04_txt));
insp_cnt_nl();
} // end: insp_do_demo
#endif
/*
* The generalized FILE utility. */
static void insp_do_file (char *fmts, int pid) {
char buf[LRGBUFSIZ];
FILE *fp;
int rc;
snprintf(buf, sizeof(buf), fmts, pid);
fp = fopen(buf, "r");
rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd);
if (fp) fclose(fp);
if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s"
, fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno)));
insp_cnt_nl();
} // end: insp_do_file
/*
* The generalized PIPE utility. */
static void insp_do_pipe (char *fmts, int pid) {
char buf[LRGBUFSIZ];
FILE *fp;
int rc;
snprintf(buf, sizeof(buf), fmts, pid);
fp = popen(buf, "r");
rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd);
if (fp) pclose(fp);
if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s"
, fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno)));
insp_cnt_nl();
} // end: insp_do_pipe
/*
* This guy supports the inspect 'L' and '&' search provisions
* and returns the row and *optimal* col for viewing any match
* ( we'll always opt for left column justification since any )
* ( preceeding control chars would consume an unknown amount ) */
static void insp_find (int ch, int *col, int *row) {
#define reDUX (found) ? N_txt(WORD_another_txt) : ""
#define begFS (int)(fnd - Insp_p[i])
static char str[SCREENMAX];
static int found;
char *fnd, *p;
int i, x, ccur = *col;
if ((ch == '&' || ch == 'n') && !str[0]) {
show_msg(N_txt(FIND_no_next_txt));
return;
}
if (ch == 'L' || ch == '/') {
strcpy(str, linein(N_txt(GET_find_str_txt)));
found = 0;
}
if (str[0]) {
INSP_BUSY;
for (i = *row; i < Insp_nl; ) {
fnd = NULL; // because our glob might
for (x = ccur +1; x < INSP_RLEN(i); x++) { // be raw binary data, we
if (!*(p = Insp_p[i] + x)) // could encounter a '\0'
continue; // in what we view as the
if ((fnd = STRSTR(p, str))) // 'row' -- so we'll have
break; // to search it in chunks
x += strlen(str); // ...
} // and, account for maybe
if (fnd && fnd < Insp_p[i +1]) { // overrunning that 'row'
found = 1;
*row = i;
*col = begFS;
return;
}
++i;
ccur = 0;
}
show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, str));
}
#undef reDUX
#undef begFS
} // end: insp_find
/*
* This guy is an insp_view_this() *Helper* function responsible
* for positioning us in both the x/y axes within the former glob
* and displaying a page worth of damages. Along the way, he makes
* sure that any control characters and/or unprintable characters
* use a less-like approach which distinguishes between two forms
* of representation: ^C and <FF>.
*
* He also creates a customized status line based on the maximum
* number of digits for the current selection's position so it will
* hopefully serve to inform, not distract by being jumpy. */
static inline void insp_show_pg (int col, int row, int max) {
#define capNO { if (hicap) { putp(Caps_off); hicap = 0; } }
#define mkCTL { if ((to += 2) <= Screen_cols) \
PUTT("%s^%c", (!hicap) ? Curwin->capclr_msg : "", uch + '@'); hicap = 1; }
#define mkUNP { if ((to += 4) <= Screen_cols) \
PUTT("%s<%02X>", (!hicap) ? Curwin->capclr_msg : "", uch); hicap = 1; }
#define mkSTD { capNO; putchar(uch); to++; }
char buf[SMLBUFSIZ];
int r = snprintf(buf, sizeof(buf), "%d", Insp_nl);
int c = snprintf(buf, sizeof(buf), "%d", col +Screen_cols);
int l = row +1, ls = Insp_nl;;
int hicap = 0;
if (!Insp_bufrd) l = ls = 0; // for a more honest representation
snprintf(buf, sizeof(buf), N_fmt(YINSP_status_fmt)
, Insp_selname
, r, l, r, ls
, c, col + 1, c, col + Screen_cols
, Insp_bufrd);
INSP_MKSL(0, buf);
for ( ; max && row < Insp_nl; row++) {
char tline[SCREENMAX];
size_t fr, to, len;
capNO;
putp("\n");
memset(tline, ' ', sizeof(tline));
len = INSP_RLEN(row);
if (col < len)
memcpy(tline, Insp_p[row] + col, sizeof(tline));
for (fr = 0, to = 0; fr < len && to < Screen_cols; fr++) {
unsigned char uch = tline[fr];
if (uch == '\n') break; // a no show (he,he)
if (uch > 126) mkUNP // show as '<AB>'
else if (uch < 32) mkCTL // show as '^C'
else mkSTD // a show off (he,he)
}
capNO;
putp(Cap_clr_eol);
--max;
}
if (max) putp(Cap_nl_clreos);
#undef capNO
#undef mkCTL
#undef mkUNP
#undef mkSTD
} // end: insp_show_pg
/*
* This guy is responsible for displaying the Insp_buf contents and
* managing all scrolling/locate requests until the user gives up. */
static int insp_view_this (char *hdr) {
#ifdef INSP_SLIDE_1
#define hzAMT 1
#else
#define hzAMT 8
#endif
#define maxLN (Screen_rows - (Msg_row +1))
char buf[SMLBUFSIZ];
int key, curlin, curcol;
for (curlin = curcol = 0;;) {
if (curcol < 0) curcol = 0;
if (curlin >= Insp_nl) curlin = Insp_nl -1;
if (curlin < 0) curlin = 0;
putp(Cap_home);
putp(Cap_curs_hide);
show_special(1, fmtmk(N_unq(INSP_hdrview_fmt), hdr));
insp_show_pg(curcol, curlin, maxLN);
switch (key = keyin(0)) {
case kbd_ENTER: // must force new keyin()
key = -1; // fall through !
case kbd_ESC: case 'q': case 0:
putp(Cap_clr_scr);
return key;
case kbd_LEFT:
curcol -= hzAMT;
break;
case kbd_RIGHT:
curcol += hzAMT;
break;
case kbd_UP:
--curlin;
break;
case kbd_DOWN:
++curlin;
break;
case kbd_PGUP: case 'b':
curlin -= maxLN -1; // keep 1 line for reference
break;
case kbd_PGDN: case kbd_SPACE:
curlin += maxLN -1; // ditto
break;
case kbd_HOME: case 'g':
curcol = curlin = 0;
break;
case kbd_END: case 'G':
curcol = 0;
curlin = Insp_nl - maxLN;
break;
case 'L': case '&': case '/': case 'n':
putp(Cap_curs_norm);
insp_find(key, &curcol, &curlin);
break;
case '=':
snprintf(buf, sizeof(buf), "%s", Insp_selfmts);
INSP_MKSL(1, buf); // show an extended SL
key = keyin(0);
if (!key) return key; // oops, we got signaled
break;
default: // keep gcc happy
break;
}
}
#undef maxLN
} // end: insp_view_this
/*
* Our driving table support, the basis for generalized inspection,
* built at startup (if at all) from rcfile or demo entries. */
struct I_entry {
void (*func)(char *, int); // a pointer to file/pipe/demo function
char *type; // the type of entry ('file' or 'pipe')
char *name; // the selection label for display
char *fmts; // format string to build path or command
int farg; // 1 = '%d' in fmts, 0 = not (future use)
const char *caps; // not really caps, show_special() delim's
};
struct I_struc {
int demo; // do NOT save table entries in rcfile
int total; // total I_entry table entries
char *raw; // all entries for 'W', incl '#' & blank
struct I_entry *tab;
};
static struct I_struc Inspect;
/*
* This is the main Inspect routine, responsible for:
* 1) validating the passed pid (required, but not always used)
* 2) presenting/establishing the target selection
* 3) arranging to fill Insp_buf (via the Inspect.tab[?].func)
* 4) invoking insp_view_this() for viewing/scrolling/searching
* 5) cleaning up the dynamically acquired memory afterwards */
static void inspection_utility (int pid) {
#define mkSEL(dst) { for (i = 0; i < Inspect.total; i++) Inspect.tab[i].caps = "~1"; \
Inspect.tab[sel].caps = "~4"; dst[0] = '\0'; \
for (i = 0; i < Inspect.total; i++) { char _s[SMLBUFSIZ]; \
snprintf(_s, sizeof(_s), " %s %s", Inspect.tab[i].name, Inspect.tab[i].caps); \
strcat(dst, _s); } }
char head[MEDBUFSIZ], sels[MEDBUFSIZ];
static int sel;
int i, key;
proc_t *p;
for (i = 0, p = NULL; i < Frame_maxtask; i++)
if (pid == Curwin->ppt[i]->tid) {
p = Curwin->ppt[i];
break;
}
if (!p) {
show_msg(fmtmk(N_fmt(YINSP_pidbad_fmt), pid));
return;
}
putp(Cap_clr_scr);
key = -1;
do {
mkSEL(sels);
putp(Cap_home);
putp(Cap_curs_hide);
snprintf(head, sizeof(head), "%s", fmtmk(N_unq(INSP_hdrbase_fmt)
, pid, p->cmd, p->euser));
show_special(1, fmtmk(N_unq(INSP_hdrsels_fmt), head, sels));
INSP_MKSL(0, " ");
if (-1 == key) key = keyin(0);
switch (key) {
case 0:
case 'q':
case kbd_ESC:
break;
case kbd_END:
sel = 0; // fall through !
case kbd_LEFT:
if (--sel < 0) sel = Inspect.total -1;
key = -1;
break;
case kbd_HOME:
sel = Inspect.total; // fall through !
case kbd_RIGHT:
if (++sel >= Inspect.total) sel = 0;
key = -1;
break;
case kbd_ENTER:
INSP_BUSY;
Insp_selname = Inspect.tab[sel].name;
Insp_selfmts = Inspect.tab[sel].fmts;
Inspect.tab[sel].func(Inspect.tab[sel].fmts, pid);
key = insp_view_this(head);
free(Insp_buf);
free(Insp_p);
break;
default: // keep gcc happy
key = -1;
break;
}
} while (key && 'q' != key && kbd_ESC != key);
#undef mkSEL
} // end: inspection_utility
#undef INSP_MKSL
#undef INSP_RLEN
#undef INSP_BUSY
/*###### Startup routines ##############################################*/ /*###### Startup routines ##############################################*/
/* /*
@ -2333,6 +2811,8 @@ static void before (char *me) {
default: default:
sa.sa_handler = sig_abexit; sa.sa_handler = sig_abexit;
break; break;
case SIGCHLD: // we can't catch this
continue; // when opening a pipe
} }
sigaction(i, &sa, NULL); sigaction(i, &sa, NULL);
} }
@ -2440,7 +2920,7 @@ static void configs_read (void) {
char fbuf[LRGBUFSIZ]; char fbuf[LRGBUFSIZ];
const char *p; const char *p;
FILE *fp; FILE *fp;
int i, x; int i;
p = getenv("HOME"); p = getenv("HOME");
snprintf(Rc_name, sizeof(Rc_name), "%s/.%src", (p && *p) ? p : ".", Myname); snprintf(Rc_name, sizeof(Rc_name), "%s/.%src", (p && *p) ? p : ".", Myname);
@ -2464,12 +2944,13 @@ static void configs_read (void) {
, "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%f, Curwin=%d\n" , "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%f, Curwin=%d\n"
, &Rc.id, &Rc.mode_altscr, &Rc.mode_irixps, &tmp_delay, &i)) { , &Rc.id, &Rc.mode_altscr, &Rc.mode_irixps, &tmp_delay, &i)) {
p = fmtmk(N_fmt(RC_bad_files_fmt), Rc_name); p = fmtmk(N_fmt(RC_bad_files_fmt), Rc_name);
goto default_or_error; goto try_inspect_entries; // maybe a faulty 'inspect' echo
} }
// you saw that, right? (fscanf stickin' it to 'i') // you saw that, right? (fscanf stickin' it to 'i')
Curwin = &Winstk[i]; Curwin = &Winstk[i];
for (i = 0 ; i < GROUPSMAX; i++) { for (i = 0 ; i < GROUPSMAX; i++) {
int x;
WIN_t *w = &Winstk[i]; WIN_t *w = &Winstk[i];
p = fmtmk(N_fmt(RC_bad_entry_fmt), i+1, Rc_name); p = fmtmk(N_fmt(RC_bad_entry_fmt), i+1, Rc_name);
@ -2509,6 +2990,64 @@ static void configs_read (void) {
// any new addition(s) last, for older rcfiles compatibility... // any new addition(s) last, for older rcfiles compatibility...
fscanf(fp, "Fixed_widest=%d\n", &Rc.fixed_widest); fscanf(fp, "Fixed_widest=%d\n", &Rc.fixed_widest);
try_inspect_entries:
// we'll start off Inspect stuff with 1 'potential' blank line
// ( only realized if we end up with Inspect.total > 0 )
for (i = 0, Inspect.raw = strdup("\n");;) {
#define iT(element) Inspect.tab[i].element
size_t lraw = strlen(Inspect.raw) +1;
char *s;
if (!fgets(fbuf, sizeof(fbuf), fp)) break;
lraw += strlen(fbuf) +1;
Inspect.raw = alloc_r(Inspect.raw, lraw);
strcat(Inspect.raw, fbuf);
if (fbuf[0] == '#' || fbuf[0] == '\n') continue;
Inspect.tab = alloc_r(Inspect.tab, sizeof(struct I_entry) * (i + 1));
p = fmtmk(N_fmt(YINSP_rcfile_fmt), i +1);
if (!(s = strtok(fbuf, "\t\n"))) goto default_or_error;
iT(type) = strdup(s);
if (!(s = strtok(NULL, "\t\n"))) goto default_or_error;
iT(name) = strdup(s);
if (!(s = strtok(NULL, "\t\n"))) goto default_or_error;
iT(fmts) = strdup(s);
switch (toupper(fbuf[0])) {
case 'F':
iT(func) = insp_do_file;
break;
case 'P':
iT(func) = insp_do_pipe;
break;
default:
goto default_or_error;
}
iT(farg) = (strstr(iT(fmts), "%d")) ? 1 : 0;
++i;
#undef iT
} // end: for ('inspect' entries)
Inspect.total = i;
#ifndef INSP_OFFDEMO
if (!Inspect.total) {
Inspect.tab = alloc_c(sizeof(struct I_entry) * 3);
Inspect.tab[0].name = strdup(N_txt(YINSP_demo01_txt));
Inspect.tab[0].func = insp_do_demo;
Inspect.tab[0].fmts = strdup(N_txt(YINSP_demo05_txt));
Inspect.tab[1].name = strdup(N_txt(YINSP_demo02_txt));
Inspect.tab[1].func = insp_do_demo;
Inspect.tab[1].fmts = strdup(N_txt(YINSP_demo05_txt));
Inspect.tab[2].name = strdup(N_txt(YINSP_demo03_txt));
Inspect.tab[2].func = insp_do_demo;
Inspect.tab[2].fmts = strdup(N_txt(YINSP_demo05_txt));
Inspect.total = 3;
Inspect.demo = 1;
}
#endif
fclose(fp); fclose(fp);
} // end: if (fp) } // end: if (fp)
@ -2585,7 +3124,8 @@ static void parse_args (char **args) {
Thread_mode = 1; Thread_mode = 1;
break; break;
case 'h': case 'h':
case 'v': case 'V': case 'v':
case 'V':
fprintf(stdout, N_fmt(HELP_cmdline_fmt) fprintf(stdout, N_fmt(HELP_cmdline_fmt)
, procps_version, Myname, N_txt(USAGE_abbrev_txt)); , procps_version, Myname, N_txt(USAGE_abbrev_txt));
bye_bye(NULL); bye_bye(NULL);
@ -3012,6 +3552,9 @@ static void file_writerc (void) {
// any new addition(s) last, for older rcfiles compatibility... // any new addition(s) last, for older rcfiles compatibility...
fprintf(fp, "Fixed_widest=%d\n", Rc.fixed_widest); fprintf(fp, "Fixed_widest=%d\n", Rc.fixed_widest);
if (!Inspect.demo && Inspect.total)
fputs(Inspect.raw, fp);
fclose(fp); fclose(fp);
show_msg(fmtmk(N_fmt(WRITE_rcfile_fmt), Rc_name)); show_msg(fmtmk(N_fmt(WRITE_rcfile_fmt), Rc_name));
} // end: file_writerc } // end: file_writerc
@ -3169,6 +3712,17 @@ static void keys_global (int ch) {
} }
} }
break; break;
case 'Y':
if (!Inspect.total)
linein(N_txt(YINSP_noents_txt));
else {
int pid, def = w->ppt[w->begtask]->tid;
if (GET_INT_BAD < (pid = get_int(fmtmk(N_fmt(YINSP_pidsee_fmt), def)))) {
if (0 > pid) pid = def;
if (pid) inspection_utility(pid);
}
}
break;
case 'Z': case 'Z':
wins_colors(); wins_colors();
break; break;
@ -3634,7 +4188,7 @@ static void do_key (int ch) {
char keys[SMLBUFSIZ]; char keys[SMLBUFSIZ];
} key_tab[] = { } key_tab[] = {
{ keys_global, { keys_global,
{ '?', 'B', 'd', 'F', 'f', 'g', 'H', 'h', 'I', 'k', 'r', 's', 'X', 'Z' { '?', 'B', 'd', 'F', 'f', 'g', 'H', 'h', 'I', 'k', 'r', 's', 'X', 'Y', 'Z'
, kbd_ENTER, kbd_SPACE, '\0' } }, , kbd_ENTER, kbd_SPACE, '\0' } },
{ keys_summary, { keys_summary,
{ '1', 'C', 'l', 'm', 't', '\0' } }, { '1', 'C', 'l', 'm', 't', '\0' } },

View File

@ -32,6 +32,9 @@
//#define CASEUP_SUFIX /* show time/mem/cnts suffix in upper case */ //#define CASEUP_SUFIX /* show time/mem/cnts suffix in upper case */
//#define CPU_ZEROTICS /* tolerate few tics when cpu off vs. idle */ //#define CPU_ZEROTICS /* tolerate few tics when cpu off vs. idle */
//#define EQUCOLHDRYES /* yes, do equalize column header lengths */ //#define EQUCOLHDRYES /* yes, do equalize column header lengths */
//#define INSP_OFFDEMO /* disable demo screens, issue msg instead */
//#define INSP_SAVEBUF /* preserve 'Insp_buf' contents in a file */
//#define INSP_SLIDE_1 /* when scrolling left/right don't move 8 */
//#define OFF_HST_HASH /* use BOTH qsort+bsrch vs. hashing scheme */ //#define OFF_HST_HASH /* use BOTH qsort+bsrch vs. hashing scheme */
//#define OFF_STDIOLBF /* disable our own stdout _IOFBF override */ //#define OFF_STDIOLBF /* disable our own stdout _IOFBF override */
//#define PERCENTBOOST /* enable extended precision for % fields */ //#define PERCENTBOOST /* enable extended precision for % fields */
@ -120,6 +123,8 @@ char *strcasestr(const char *haystack, const char *needle);
many termcap/color transitions - these definitions ensure we have room */ many termcap/color transitions - these definitions ensure we have room */
#define ROWMINSIZ ( SCREENMAX + 4 * (CAPBUFSIZ + CLRBUFSIZ) ) #define ROWMINSIZ ( SCREENMAX + 4 * (CAPBUFSIZ + CLRBUFSIZ) )
#define ROWMAXSIZ ( SCREENMAX + 16 * (CAPBUFSIZ + CLRBUFSIZ) ) #define ROWMAXSIZ ( SCREENMAX + 16 * (CAPBUFSIZ + CLRBUFSIZ) )
// minimum size guarantee for dynamically acquired 'readfile' buffer
#define READMINSZ 2048
// space between task fields/columns // space between task fields/columns
#define COLPADSTR " " #define COLPADSTR " "
@ -598,12 +603,13 @@ typedef struct WIN_t {
//atic inline void show_scroll (void); //atic inline void show_scroll (void);
//atic void show_special (int interact, const char *glob); //atic void show_special (int interact, const char *glob);
//atic void updt_scroll_msg (void); //atic void updt_scroll_msg (void);
/*------ Low Level Memory/Keyboard support -----------------------------*/ /*------ Low Level Memory/Keyboard/File I/O support --------------------*/
//atic void *alloc_c (size_t num); //atic void *alloc_c (size_t num);
//atic void *alloc_r (void *ptr, size_t num); //atic void *alloc_r (void *ptr, size_t num);
//atic int chin (int ech, char *buf, unsigned cnt); //atic int chin (int ech, char *buf, unsigned cnt);
//atic int keyin (int init); //atic int keyin (int init);
//atic char *linein (const char *prompt); //atic char *linein (const char *prompt);
//atic int readfile (FILE *fp, char **baddr, unsigned *bsize, unsigned *bread);
/*------ Small Utility routines ----------------------------------------*/ /*------ Small Utility routines ----------------------------------------*/
//atic float get_float (const char *prompt); //atic float get_float (const char *prompt);
//atic int get_int (const char *prompt); //atic int get_int (const char *prompt);
@ -638,6 +644,17 @@ typedef struct WIN_t {
//atic void procs_hlp (proc_t *p); //atic void procs_hlp (proc_t *p);
//atic void procs_refresh (void); //atic void procs_refresh (void);
//atic void sysinfo_refresh (int forced); //atic void sysinfo_refresh (int forced);
/*------ Inspect Other Output ------------------------------------------*/
//atic void insp_cnt_nl (void);
#ifndef INSP_OFFDEMO
//atic void insp_do_demo (char *fmts, int pid);
#endif
//atic void insp_do_file (char *fmts, int pid);
//atic void insp_do_pipe (char *fmts, int pid);
//atic void insp_find (int ch, int *col, int *row);
//atic inline void insp_show_pg (int col, int row, int max);
//atic int insp_view_this (char *hdr);
//atic void inspection_utility (int pid);
/*------ Startup routines ----------------------------------------------*/ /*------ Startup routines ----------------------------------------------*/
//atic void before (char *me); //atic void before (char *me);
//atic int config_cvt (WIN_t *q); //atic int config_cvt (WIN_t *q);

View File

@ -371,6 +371,55 @@ static void build_norm_nlstab (void) {
#ifndef WARN_CFG_OFF #ifndef WARN_CFG_OFF
Norm_nlstab[XTRA_warncfg_txt] = _("Overwrite existing old style rcfile?"); Norm_nlstab[XTRA_warncfg_txt] = _("Overwrite existing old style rcfile?");
#endif #endif
#ifndef INSP_OFFDEMO
Norm_nlstab[YINSP_demo01_txt] = _("Open Files");
Norm_nlstab[YINSP_demo02_txt] = _("NUMA Info");
Norm_nlstab[YINSP_demo03_txt] = _("Log");
Norm_nlstab[YINSP_demo04_txt] = _(""
"This is simulated output representing the contents of some file or the output\n"
"from some command. Exactly which commands and/or files are solely up to you.\n"
"\n"
"Although this text is for information purposes only, it can still be scrolled\n"
"and searched like real output will be. You are encouraged to experiment with\n"
"those features as explained in the prologue above.\n"
"\n"
"To enable real Inspect functionality, entries must be added to the end of the\n"
"top personal personal configuration file. You could use your favorite editor\n"
"to accomplish this, taking care not to disturb existing entries.\n"
"\n"
"Another way to add entries is illustrated below, but it risks overwriting the\n"
"rcfile. Redirected echoes must not replace (>) but append (>>) to that file.\n"
"\n"
" /bin/echo -e \"pipe\\tOpen Files\\tlsof -P -p %d 2>&1\" >> ~/.toprc\n"
" /bin/echo -e \"file\\tNUMA Info\\t/proc/%d/numa_maps\" >> ~/.toprc\n"
" /bin/echo -e \"pipe\\tLog\\ttail -n200 /var/log/syslog | sort -Mr\" >> ~/.toprc\n"
"\n"
"If you don't know the location or name of the top rcfile, use the 'W' command\n"
"and note those details. After backing up the current rcfile, try issuing the\n"
"above echoes exactly as shown, replacing '.toprc' as appropriate. The safest\n"
"approach would be to use copy then paste to avoid any typing mistakes.\n"
"\n"
"Finally, restart top to reveal what actual Inspect entries combined with this\n"
"new command can offer. The possibilities are endless, especially considering\n"
"that 'pipe' type entries can include shell scripts too!\n"
"\n"
"For additional important information, please consult the top documentation.\n"
"Then enhance top with your very own customized 'file' and 'pipe' entries.\n"
"\n"
"Enjoy!\n");
Norm_nlstab[YINSP_demo05_txt] = _("the '=' key will eventually show the actual file read or command(s) excuted ...");
#endif
Norm_nlstab[YINSP_failed_fmt] = _("Selection failed with: %s\n");
#ifndef INSP_OFFDEMO
Norm_nlstab[YINSP_noents_txt] = _("to enable 'Y' press <Enter> then type 'W' and restart top");
#else
Norm_nlstab[YINSP_noents_txt] = _("to enable 'Y' please consult the top man page (press Enter)");
#endif
Norm_nlstab[YINSP_pidbad_fmt] = _("unable to inspect, pid %d not found");
Norm_nlstab[YINSP_pidsee_fmt] = _("inspect at PID [defailt pid = %d]");
Norm_nlstab[YINSP_rcfile_fmt] = _("could not parse rcfile inspect entry %d");
Norm_nlstab[YINSP_status_fmt] = _("%s: %*d-%-*d lines, %*d-%*d columns, %u bytes read");
Norm_nlstab[YINSP_workin_txt] = _("patience please, working...");
} }
@ -400,7 +449,7 @@ static void build_uniq_nlstab (void) {
. adhere to that goal lest the translated text be truncated. . adhere to that goal lest the translated text be truncated.
. .
. If you would like additional information regarding these strings, . If you would like additional information regarding these strings,
. please see the prolog to the show_special function in the top.c . please see the prologue to the show_special function in the top.c
. source file. . source file.
. */ . */
@ -413,17 +462,17 @@ static void build_uniq_nlstab (void) {
" 1,I Toggle SMP view: '~11~2' single/separate states; '~1I~2' Irix/Solaris mode\n" " 1,I Toggle SMP view: '~11~2' single/separate states; '~1I~2' Irix/Solaris mode\n"
" f,F,X Fields: '~1f~2'/'~1F~2' add/remove/order/sort; '~1X~2' increase fixed-width\n" " f,F,X Fields: '~1f~2'/'~1F~2' add/remove/order/sort; '~1X~2' increase fixed-width\n"
"\n" "\n"
" L,&,<,> . Locate: '~1L~2'/'~1&~2' find/again; Move sort column: '~1<~2'/'~1>~2' left/right\n" \ " L,&,<,> . Locate: '~1L~2'/'~1&~2' find/again; Move sort column: '~1<~2'/'~1>~2' left/right\n"
" R,H,V,J . Toggle: '~1R~2' Sort; '~1H~2' Threads; '~1V~2' Forest view; '~1J~2' Num justify\n" " R,H,V,J . Toggle: '~1R~2' Sort; '~1H~2' Threads; '~1V~2' Forest view; '~1J~2' Num justify\n"
" c,i,S,j . Toggle: '~1c~2' Cmd name/line; '~1i~2' Idle; '~1S~2' Time; '~1j~2' Str justify\n" " c,i,S,j . Toggle: '~1c~2' Cmd name/line; '~1i~2' Idle; '~1S~2' Time; '~1j~2' Str justify\n"
" x~5,~1y~5 . Toggle highlights: '~1x~2' sort field; '~1y~2' running tasks\n" " x~5,~1y~5 . Toggle highlights: '~1x~2' sort field; '~1y~2' running tasks\n"
" z~5,~1b~5 . Toggle: '~1z~2' color/mono; '~1b~2' bold/reverse (only if 'x' or 'y')\n" " z~5,~1b~5 . Toggle: '~1z~2' color/mono; '~1b~2' bold/reverse (only if 'x' or 'y')\n"
" u,U . Show: '~1u~2' effective user; '~1U~2' real, saved, file or effective user\n" " u,U . Filter by: '~1u~2' effective user; '~1U~2' real, saved, file or effective user\n"
" n or # . Set maximum tasks displayed\n" " n or # . Set maximum tasks displayed\n"
" C,... . Toggle scroll coordinates msg for: ~1up~2,~1down~2,~1left~2,right~2,~1home~2,~1end~2\n" " C,... . Toggle scroll coordinates msg for: ~1up~2,~1down~2,~1left~2,right~2,~1home~2,~1end~2\n"
"\n" "\n"
"%s" "%s"
" W Write configuration file\n" " W,Y Write configuration file '~1W~2'; Inspect other output '~1Y~2'\n"
" q Quit\n" " q Quit\n"
" ( commands shown with '.' require a ~1visible~2 task display ~1window~2 ) \n" " ( commands shown with '.' require a ~1visible~2 task display ~1window~2 ) \n"
"Press '~1h~2' or '~1?~2' for help with ~1Windows~2,\n" "Press '~1h~2' or '~1?~2' for help with ~1Windows~2,\n"
@ -544,6 +593,19 @@ static void build_uniq_nlstab (void) {
Uniq_nlstab[MEMORY_lines_fmt] = _("" Uniq_nlstab[MEMORY_lines_fmt] = _(""
"%s Mem: ~3 %8lu ~2total,~3 %8lu ~2used,~3 %8lu ~2free,~3 %8lu ~2buffers~3\n" "%s Mem: ~3 %8lu ~2total,~3 %8lu ~2used,~3 %8lu ~2free,~3 %8lu ~2buffers~3\n"
"%s Swap:~3 %8lu ~2total,~3 %8lu ~2used,~3 %8lu ~2free,~3 %8lu ~2cached~3\n"); "%s Swap:~3 %8lu ~2total,~3 %8lu ~2used,~3 %8lu ~2free,~3 %8lu ~2cached~3\n");
Uniq_nlstab[INSP_hdrbase_fmt] = _(""
"Inspection~2 Pause at: pid ~1%d~6 running command ~1%s~6 as user ~1%s~6");
Uniq_nlstab[INSP_hdrsels_fmt] = _(""
"%s\n"
"Use~2: left/right then <Enter> to ~1select~5 an option; 'q' or <Esc> to ~1end~5 !\n"
"Options~2: ~1%s\n");
Uniq_nlstab[INSP_hdrview_fmt] = _(""
"%s\n"
"Use~2: left/right/up/down/etc to ~1navigate~5 the output; 'L'/'&' to ~1locate~5/~1next~5.\n"
"Or~2: <Enter> to ~1select another~5; 'q' or <Esc> to ~1end~5 !\n");
} }
@ -602,6 +664,16 @@ void initialize_nls (void) {
fprintf(stderr, nls_err, "Uniq", i); fprintf(stderr, nls_err, "Uniq", i);
exit(1); exit(1);
} }
#ifndef INSP_OFFDEMO
if (READMINSZ < strlen(N_txt(YINSP_demo04_txt)) +1) {
fprintf(stderr
, "\nAssertion Failed in %s (%s):\n"
"\t'READMINSZ < strlen(N_txt(YINSP_demo04_txt)) + 1'\n"
"READMINSZ must be at least %u !\n\n"
, __FILE__, __func__, (unsigned)strlen(N_txt(YINSP_demo04_txt)) + 1);
exit(1);
}
#endif
#else #else
setlocale (LC_ALL, ""); setlocale (LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR); bindtextdomain(PACKAGE, LOCALEDIR);

View File

@ -84,13 +84,20 @@ enum norm_nls {
#ifndef WARN_CFG_OFF #ifndef WARN_CFG_OFF
XTRA_warncfg_txt, XTRA_warncfg_txt,
#endif #endif
#ifndef INSP_OFFDEMO
YINSP_demo01_txt, YINSP_demo02_txt, YINSP_demo03_txt, YINSP_demo04_txt,
YINSP_demo05_txt,
#endif
YINSP_failed_fmt, YINSP_noents_txt, YINSP_pidbad_fmt, YINSP_pidsee_fmt,
YINSP_rcfile_fmt, YINSP_status_fmt, YINSP_workin_txt,
norm_MAX norm_MAX
}; };
enum uniq_nls { enum uniq_nls {
KEYS_helpbas_fmt, KEYS_helpext_fmt, WINDOWS_help_fmt, COLOR_custom_fmt, KEYS_helpbas_fmt, KEYS_helpext_fmt, WINDOWS_help_fmt, COLOR_custom_fmt,
FIELD_header_fmt, MEMORY_lines_fmt, STATE_line_1_fmt, STATE_lin2x4_fmt, FIELD_header_fmt, MEMORY_lines_fmt, STATE_line_1_fmt, STATE_lin2x4_fmt,
STATE_lin2x5_fmt, STATE_lin2x6_fmt, STATE_lin2x7_fmt, STATE_lin2x5_fmt, STATE_lin2x6_fmt, STATE_lin2x7_fmt, INSP_hdrbase_fmt,
INSP_hdrsels_fmt, INSP_hdrview_fmt,
uniq_MAX uniq_MAX
}; };