1
0
Files
irix-657m-src/eoe/cmd/bloatview/bloatview.c
2022-09-29 17:59:04 +03:00

946 lines
20 KiB
C

/*
* bloatview.c
*
* Reveal the memory usage of bloated (and non-bloated) software
*/
#include <sys/types.h>
#include <sys/time.h>
#include <sys/sysmp.h>
#include <sys/wait.h>
#include <sys/capability.h>
#include <unistd.h>
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include <bstring.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include "process.h"
#include "draw.h"
#include "inode.h"
#include "print.h"
#define DEF_THRESH 500
#define DEF_DELTA 50
/*
* Set to 1 once the inode table has been initialized
*/
static int inodesInitted = 0;
#ifdef CAP_DEBUG
/*
* void
* PrintCapabilities(const char* prefix)
*
* Description:
* Function for debugging capabilities.
*/
void
PrintCapabilities(const char* prefix)
{
char *text;
cap_t cap = cap_get_proc();
text = cap_to_text(cap, NULL);
fprintf(stderr, "%s: %s\n", prefix, text);
cap_free(text);
}
#endif
/*
* static void
* InitCapabilities(void)
*
* Description:
* Set up our set of permitted capabilities, and then surrender
* our setuid root status. The capabilities we set in our
* permitted set correspond to those used by the procfs code.
* They do not get moved into our effective set until we need
* them (see cap_open in process.c).
*
* Setting CAP_FLAG_PURE_RECALC in our inheritable set causes
* programs that we exec to inherit no capabilities from us.
*/
static void
InitCapabilities(void)
{
cap_t cap = cap_from_text("all= "
"CAP_DAC_WRITE,CAP_DAC_READ_SEARCH,CAP_FOWNER+p");
cap_set_proc(cap);
cap_free(cap);
cap_set_proc_flags(CAP_FLAG_PURE_RECALC);
setuid(getuid());
#ifdef CAP_DEBUG
PrintCapabilities("gmemusage");
#endif
}
/*
* static void
* PlaySound(char *sound)
*
* Description:
* run playaiff asynchronously to play a sound
*
* Parameters:
* sound
*/
static void
PlaySound(char *sound)
{
static int firstTime = 1;
static int ok = 0;
static pid_t pid;
int fd, stat;
if (firstTime) {
firstTime = 0;
if (sound) {
if (access("/usr/sbin/playaiff", F_OK | X_OK) == -1) {
perror("/usr/sbin/playiff");
return;
}
if (access(sound, F_OK) == -1) {
perror(sound);
return;
}
ok = 1;
}
}
if (!ok) {
return;
}
/*
* Don't interrupt it if the last one is still playing
*/
if (waitpid(pid, &stat, WNOHANG) == 0) {
return;
}
if (pid = fork()) {
return;
}
for (fd = getdtablesize(); fd > 2; fd--) {
close(fd);
}
setuid(getuid()); /* avoid security problems */
execl("/usr/sbin/playaiff", "playaiff", sound, NULL);
exit(-1);
}
/*
* static void
* InitInodes(void)
*
* Description:
* Make sure inodes are initted. This gets called from two
* different places, and the flag inodesInitted is global.
*/
static void
InitInodes(void)
{
if (!inodesInitted) {
WaitMessage("Initializing inode lookup table...", NULL);
InodeInit();
inodesInitted = 1;
}
}
/*
* static void
* RegionView(pid_t pid, void *vaddr)
*
* Description:
* If region viewer is not running, start it.
*
* Write on the pipe which is region viewer's input the process
* id and virtual address of the region to monitor.
*
* If we just started region viewer, wait for its window to come
* up.
*
* Parameters:
* pid process id to monitor
* vaddr virtual address of the region in pid to monitor
*/
static void
RegionView(pid_t pid, void *vaddr)
{
char buf[20];
int pipes[2], errorPipe[2], fd, err, n, conn, seenIt;
static int wpipe = -1;
pid_t p;
Display *dpy;
XEvent event;
fd_set readfds;
struct timeval tv;
(void)snprintf(buf, sizeof buf, "%d %x\n", pid, vaddr);
if (wpipe == -1
|| write(wpipe, buf, strlen(buf)) == -1 && oserror() == EPIPE) {
WaitMessage("Launching Region Viewer...", NULL);
if (wpipe != -1) {
(void)close(wpipe);
wpipe = -1;
}
/*
* Create pipe for sending commands to regview
*/
if (pipe(pipes) == -1) {
perror("pipe");
return;
}
/*
* Create pipe that we use to tell if regview even got off the
* ground, using fcntl FD_CLOEXEC.
*/
if (pipe(errorPipe) == -1) {
perror("pipe");
return;
}
/*
* Get an X connection before forking to avoid race conditions
*/
dpy = XOpenDisplay(NULL);
if (dpy != NULL) {
XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureNotifyMask);
XFlush(dpy);
}
p = fork();
if (p == -1) {
perror("fork");
(void)close(pipes[0]);
(void)close(pipes[1]);
(void)close(errorPipe[0]);
(void)close(errorPipe[1]);
XCloseDisplay(dpy);
return;
}
if (p == 0) {
/*
* Child execs to become regview. stdin of regview is the
* read end of the pipe we created for sending commands.
*/
dup2(pipes[0], 0);
(void)close(errorPipe[0]);
if (errorPipe[1] != 3) {
dup2(errorPipe[1], 3);
}
/*
* Setting FD_CLOEXEC will cause the read in the parent
* below to return 0 if the exec goes OK. Otherwise,
* we'll write something on the pipe and our parent will
* know something went wrong.
*/
(void)fcntl(3, F_SETFD, FD_CLOEXEC);
for (fd = getdtablehi(); fd > 3; fd--) {
(void)close(fd);
}
execl("/usr/lib/regview", "regview", NULL);
perror("/usr/lib/regview");
/*
* Notify parent process that something has gone afoul, so
* that it doesn't wait for our window to be mapped.
*/
err = 1;
(void)write(3, &err, sizeof err);
}
(void)close(pipes[0]);
(void)close(errorPipe[1]);
/*
* Tell new regview pid and vaddr to monitor
*/
wpipe = pipes[1];
(void)write(wpipe, buf, strlen(buf));
/*
* If things are cool, read should return 0 because the pipe
* was closed during exec.
*/
n = read(errorPipe[0], &err, sizeof err);
(void)close(errorPipe[0]);
if (dpy == NULL) {
return;
}
/*
* Wait for a map event on the X root window
*/
if (n == 0) {
conn = ConnectionNumber(dpy);
seenIt = 0;
while (1) {
while (XPending(dpy)) {
XNextEvent(dpy, &event);
if (event.type == MapNotify) {
seenIt = 1;
break;
}
}
if (seenIt) {
break;
}
tv.tv_sec = 5;
tv.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET(conn, &readfds);
if (select(conn + 1, &readfds, NULL, NULL, &tv) != 1) {
break;
}
}
}
XCloseDisplay(dpy);
}
}
/*
* static char *
* HandleMouse(PROGRAM *bloat, long mousex, long mousey, int *procMode,
* int dragging)
*
* Description:
* Handle mouse events. We use the mouse to select bars to
* display detailed breakdowns, and to leave detailed mode. The
* passed in procMode gets cleared if we should leave detailed
* mode.
*
* Parameters:
* bloat current all programs bloat being displayed
* mousex mouse horiz position
* mousey mouse vert position
* procMode pointer to variable that says whether we're already
* in procMode
* dragging 1 if we're dragging
*
* Returns:
* pointer to name of program selected
*/
static char *
HandleMouse(PROGRAM *bloat, long mousex, long mousey, int *procMode,
int dragging)
{
static char *oldProcName;
char *procName;
procName = Select(bloat, mousex, mousey, procMode, dragging);
if (!procName && *procMode) {
return oldProcName;
}
if (procName) {
if (!inodesInitted && strcmp(procName, IRIX) != 0) {
InitInodes();
}
*procMode = 1;
if (oldProcName) {
free(oldProcName);
}
oldProcName = procName;
}
return procName;
}
/*
* static int XIOError(Display *dpy)
*
* Description:
* Error handler that gets called when an I/O error occurs. The
* purpose of this function is to avoid printing:
*
* XIO: fatal IO error 131 (Connection reset by peer) on X server ":0.0"
* after 7919 requests (6854 known processed) with 0 events remaining.
*
* When the user closes the window.
*/
/*ARGSUSED*/
static int XIOError(Display *dpy)
{
exit(0);
return 0;
}
/*
* Show how bloated IRIX has become
*/
void
main(int argc, char *argv[])
{
int xfd, opt, threshDelta = DEF_DELTA, doHelp = 0, doFreeze = 0;
int gotInput, doProcMode = 0, needNewColors = 0;
int barTotal, progBarTotal, numBars, numProgBars;
int oldProgBarTotal, soundThresh = 12, pgsize;
int printModeOn = 0;
fd_set fds, tfds;
PROGRAM *bloat = NULL, *lastBloat = NULL, *b;
PROGRAM *progBloat = NULL, *lastProgBloat = NULL;
long physMem, freeMem, threshHold = DEF_THRESH;
struct timeval tv;
char *procName = NULL, *pn;
pid_t procPid = -1, pid;
PROGNAME *progNames = NULL, *name, *prev = NULL;
BloatType type, newType;
SecondType stype, newStype;
long usecTimeOut = 0, secTimeOut = 1, msecTimeOut;
struct rminfo rminfo;
char *bloatSound = getenv("GMEMUSAGESOUND");
PIDLIST *pids;
sigaction_t act;
Display *dpy;
XEvent xevent;
char keyBuf[10];
KeySym keySym;
if (geteuid() != 0) {
fprintf(stderr, "%s must be run as root or it must be setuid root.\n",
argv[0]);
exit(1);
}
InitCapabilities();
act.sa_handler = SIG_IGN;
act.sa_flags = 0;
(void)sigemptyset(&act.sa_mask);
(void)sigaction(SIGPIPE, &act, NULL);
act.sa_handler = SIG_IGN;
act.sa_flags = SA_NOCLDWAIT;
(void)sigemptyset(&act.sa_mask);
(void)sigaction(SIGCHLD, &act, NULL);
#if 0
mallopt(M_FREEHD, 1);
#endif
type = newType = Physical;
stype = newStype = Nostype;
/*
* Process command line arguments
*/
opterr = 0;
while ((opt = getopt(argc, argv, "a:d:f:g:i:mnprst:uyv")) != EOF) {
switch (opt) {
case 'a':
bloatSound = optarg;
break;
case 'f':
SetFontName(optarg);
break;
case 'g':
soundThresh = atoi(optarg);
break;
case 'i':
msecTimeOut = atoi(optarg);
secTimeOut = msecTimeOut / 1000;
usecTimeOut = 1000 * (msecTimeOut % 1000);
break;
case 'd':
threshDelta = atoi(optarg);
if (threshDelta <= 0) {
fprintf(stderr, "Resetting delta from %d\n", threshDelta);
threshDelta = DEF_DELTA;
}
break;
case 'm':
type = newType = MappedObjects;
break;
case 'n':
SetNoDoubleBuffer();
break;
case 'p':
type = newType = Physical;
break;
case 'r':
type = newType = Resident;
break;
case 's':
type = newType = Size;
break;
case 't':
threshHold = atoi(optarg);
break;
case 'u':
InvalidateInodeTable();
break;
case 'v':
UseDefaultVisual();
break;
case 'y':
printModeOn = 1;
break;
default:
doHelp = 1;
}
}
while (optind < argc) {
name = malloc(sizeof *name);
name->name = argv[optind];
name->next = NULL;
if (prev) {
prev->next = name;
} else {
progNames = name;
}
prev = name;
optind++;
}
if (sysmp(MP_SAGET, MPSA_RMINFO, &rminfo, sizeof rminfo) == -1) {
perror("sysmp");
exit(1);
}
pgsize = getpagesize()/1024;
physMem = rminfo.physmem * pgsize;
dpy = XOpenDisplay(NULL);
if (dpy == NULL) {
fprintf(stderr, "Unable to open X display\n");
exit(1);
}
XSetIOErrorHandler(XIOError);
ShowPrintMode(printModeOn);
Init(argc, argv, dpy,
ExposureMask | KeyReleaseMask | ButtonPressMask
| StructureNotifyMask | Button1MotionMask
| VisibilityChangeMask, progNames, threshHold);
/*
* Wait for first expose event so we're not spending a lot of time
* in process.c while our window is blank.
*/
while (1) {
XNextEvent(dpy, &xevent);
if (xevent.type == Expose) {
break;
}
}
xfd = ConnectionNumber(dpy);
FD_ZERO(&fds);
FD_SET(xfd, &fds);
tv.tv_sec = secTimeOut;
tv.tv_usec = usecTimeOut;
gotInput = 0;
if (type == MappedObjects) {
InitInodes();
}
while (1) {
/*
* Draw the window, either help or the bloat chart
*/
if (!doFreeze) {
if (doHelp) {
Help();
} else {
/*
* If the user pressed a key that we can deal with without
* getting all our data again, just redraw.
*/
if (!gotInput || type != newType || stype != newStype) {
stype = newStype;
type = newType;
if (lastBloat) {
FreeBloat(lastBloat);
}
lastBloat = bloat;
if (lastProgBloat) {
FreeBloat(lastProgBloat);
}
lastProgBloat = progBloat;
if (type == MappedObjects) {
GetObjInfo(doProcMode ? procName : NULL,
&bloat, &progBloat);
} else {
pn = doProcMode || procPid == -1 ? procName : NULL;
pid = doProcMode ? procPid : -1;
GetProcInfo(pn, pid, &bloat, &progBloat);
}
if (sysmp(MP_SAGET, MPSA_RMINFO, &rminfo,
sizeof rminfo) == -1) {
perror("sysmp");
exit(1);
}
freeMem = rminfo.freemem * pgsize;
if (bloat) {
bloat = DrawSetup(bloat,
needNewColors ? NULL : lastBloat,
physMem, freeMem, type, stype, 1,
&barTotal, &numBars);
}
if (progBloat) {
progBloat = DrawSetup(progBloat,
needNewColors ?
NULL : lastProgBloat,
physMem, freeMem,
type, stype, 0, &progBarTotal,
&numProgBars);
if (bloatSound) {
if (oldProgBarTotal == 0 ||
oldProgBarTotal > progBarTotal) {
oldProgBarTotal = progBarTotal;
} else if (progBarTotal >
oldProgBarTotal + soundThresh) {
PlaySound(bloatSound);
oldProgBarTotal = progBarTotal;
}
}
}
/*
* If the process we were looking at goes away,
* get out of process mode.
*/
if (doProcMode && !progBloat) {
doProcMode = 0;
procName = NULL;
procPid = -1;
progBarTotal = 0;
}
needNewColors = 0;
}
if (doProcMode && progBloat) {
DrawShadow(bloat, barTotal, procName);
Draw(progBloat, progBarTotal, numProgBars,
procName, procPid, type, stype);
} else if (bloat) {
Draw(bloat, barTotal, numBars, NULL, -1, type, stype);
}
gotInput = 0;
}
if (printModeOn) {
if (doProcMode && progBloat) {
PrintProcBloat(stdout, progBloat,
procName, procPid);
} else if (bloat) {
if (type == MappedObjects) {
PrintMapBloat(stdout, bloat);
} else {
PrintAllBloat(stdout, bloat);
}
}
}
}
XFlush(dpy);
/*
* Wait for X events, timing out after half a second (or user
* specified timeout value).
*/
tfds = fds;
if (select(xfd + 1, &tfds, NULL, NULL,
(doHelp || doFreeze) ? NULL : &tv) < 0) {
perror("select");
exit(1);
}
/*
* Process X events
*/
if (FD_ISSET(xfd, &tfds)) {
while (XPending(dpy)) {
XNextEvent(dpy, &xevent);
switch (xevent.type) {
case ConfigureNotify:
Resize(xevent.xconfigure.width,
xevent.xconfigure.height);
gotInput = 1;
break;
case Expose:
gotInput = 1;
break;
case MotionNotify:
needNewColors = 1;
procName = HandleMouse(bloat, xevent.xmotion.x,
xevent.xmotion.y,
&doProcMode, 1);
procPid = -1;
oldProgBarTotal = 0;
break;
case ButtonPress:
if (xevent.xbutton.button == 1) {
needNewColors = 1;
if (doProcMode && procName && progBloat) {
PROGRAM *region;
region = SelectRegion(progBloat,
xevent.xbutton.x,
xevent.xbutton.y);
if (region && region->vaddr) {
pid = -1;
if (procPid != -1) {
pid = procPid;
} else {
b = bloat;
while (b && strcmp(b->progName,
procName) != 0) {
b = b->next;
}
if (b) {
pid = b->pid;
}
}
if (pid != -1) {
RegionView(pid, region->vaddr);
break;
}
}
}
if (bloat && !doHelp) {
procName = HandleMouse(bloat,
xevent.xbutton.x,
xevent.xbutton.y,
&doProcMode, 0);
procPid = -1;
oldProgBarTotal = 0;
if (procName && strcmp(procName, IRIX) == 0) {
newStype = Nostype;
}
}
}
break;
case MapNotify:
doFreeze = 0;
break;
case UnmapNotify:
doFreeze = 1;
break;
case KeyRelease:
XLookupString(&xevent.xkey, keyBuf, sizeof keyBuf,
&keySym, NULL);
switch (keySym) {
#ifdef CAP_DEBUG
case XK_d:
system("./capdebug");
system("id");
break;
#endif
case XK_h:
/*
* Help
*/
doHelp = 1;
needNewColors = 1;
break;
case XK_space:
/*
* End of help
*/
doProcMode = 0;
doHelp = 0;
break;
case XK_m:
newStype = stype % Phys;
newType = MappedObjects;
doHelp = 0;
InitInodes();
needNewColors = 1;
break;
case XK_p:
/*
* Physical Memory Breakdown
*/
if (procName && strcmp(procName, IRIX) == 0) {
newStype = Nostype;
} else {
newStype = stype % Phys;
}
newType = Physical;
doHelp = 0;
needNewColors = 1;
break;
case XK_r:
/*
* Resident Sizes of Processes
*/
if (!doProcMode
|| strcmp(procName, IRIX) != 0) {
newStype = stype % Res;
newType = Resident;
doHelp = 0;
needNewColors = 1;
}
break;
case XK_s:
/*
* Total Sizes of Processes
*/
if (!doProcMode
|| strcmp(procName, IRIX) != 0) {
newStype = stype % (Res + 1);
newType = Size;
doHelp = 0;
needNewColors = 1;
}
break;
case XK_t:
if (doProcMode && progBloat) {
PrintProcBloat(stdout, progBloat,
procName, procPid);
} else if (bloat) {
if (type == MappedObjects) {
PrintMapBloat(stdout, bloat);
} else {
PrintAllBloat(stdout, bloat);
}
}
break;
case XK_v:
doHelp = 0;
newStype = stype + 1;
switch (type) {
case Size:
newStype %= Res + 1;
break;
case Resident:
newStype %= Res;
break;
case Physical:
if (procName && strcmp(procName, IRIX) == 0) {
newStype = Nostype;
break;
}
/* intentional fall through */
case MappedObjects:
newStype %= Phys;
break;
default:
newStype = Nostype;
break;
}
break;
case XK_y:
printModeOn = !printModeOn;
ShowPrintMode(printModeOn);
break;
case XK_Escape:
/*
* Exit
*/
exit(0);
break;
case XK_Up:
/*
* Increase threshhold
*/
if (threshHold == 1)
threshHold = 0;
threshHold += threshDelta;
SetThreshHold(threshHold);
doHelp = 0;
needNewColors = 1;
break;
case XK_Down:
/*
* Decrease threshhold
*/
if (threshHold == threshDelta)
threshHold = 1;
else
threshHold -= threshDelta;
if (threshHold < 0) {
threshHold = 0;
}
SetThreshHold(threshHold);
doHelp = 0;
needNewColors = 1;
break;
case XK_Page_Up:
if (doProcMode && procPid != -1
&& stype != MappedObjects) {
b = bloat;
while (b && strcmp(b->progName, procName) != 0) {
b = b->next;
}
if (b) {
pids = b->pids;
while (pids && pids->pid != procPid) {
pids = pids->next;
}
if (pids && pids->prev) {
procPid = pids->prev->pid;
} else {
procPid = -1;
}
} else {
procPid = -1;
}
}
break;
case XK_Page_Down:
if (doProcMode && stype != MappedObjects) {
b = bloat;
while (b && strcmp(b->progName, procName) != 0) {
b = b->next;
}
if (b && b->pids) {
pids = b->pids;
if (procPid == -1) {
procPid = pids->pid;
} else {
while (pids && pids->pid != procPid) {
pids = pids->next;
}
if (pids && pids->next) {
procPid = pids->next->pid;
}
}
} else {
procPid = -1;
}
}
break;
}
}
}
}
}
}