1
0
Files
2022-09-29 17:59:04 +03:00

1841 lines
44 KiB
C

/*
* draw.c
*
* Drawing bloat
*/
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xdbe.h>
#include <X11/Xatom.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <bstring.h>
#include "process.h"
#include "draw.h"
#define FREEPID (-1) /* smaller than any valid pid */
#define OTHERPID 0x00010000 /* bigger than any valid pid */
#define IRIXPID 0x00010001 /* bigger than OTHERPID */
#define FREECOLOR (GREEN + 8)
#define IRIXCOLOR (MAGENTA + 8)
#define MARG_NUM 2
#define BAR_NUM 5
#define LABEL_NUM 13
#define LINELEFT_NUM 8
#define LINERIGHT_NUM 12
#define DENOM 26
#define YSHADOWOFF 25
#define XSHADOWOFF 25
#define MINWIDTH 600
#define MINHEIGHT 600
#define TEXTWIDTH 236
#define SLABELWIDTH 88
#define FONTWIDTH 9
#define FONTHEIGHT 15
#define NUMCOLORS 9
#define MIN(a,b) ((a) < (b) ? (a) : (b))
static unsigned long barColors[6];
static int usingDBE = 0;
static Drawable backBuffer;
#define NBARCOLORS (sizeof barColors/sizeof *barColors - 2)
#define FREEINDEX NBARCOLORS
#define IRIXINDEX (NBARCOLORS + 1)
static long winWidth, winHeight, barWidth, hmargin, barHeight;
static long topMargin, bottomMargin;
static long labelLeft, lineLeft, lineRight, memThresh;
static long labelTop, labelBottom;
static int textHeight, halfTextHeight;
static unsigned long bgColor;
static unsigned long textColor;
static unsigned long black;
static unsigned long otherColor = -1;
static PROGNAME *drawNames;
static Display *theDpy;
static Window theWin;
static int useDefaultVisual;
static int doubleBuffer = 1;
static Visual *theVisual;
static int theDepth;
static GC theGC;
static const char *theFontName
= "-*-medium-r-normal--15-150-*-*-*-90-iso8859-1";
static XFontStruct *theFont;
static int curX, curY;
static int printMode;
static int blackAndWhiteMode;
static char *titles[] = {
"Physical Memory Breakdown",
"Resident Sizes of Processes",
"Total Sizes of Processes",
"Resident Mappings",
};
static char *progTitles[] = {
"Physical Memory use for %s (%s)",
"Resident Size of %s (%s)",
"Total Size of %s (%s)",
"Resident Mapping Breakdown of %s",
};
static char *captions[] = {
"Physical Memory: %d K (%d K used)",
"Sum of Resident Sizes: %d K",
"Sum of Total Sizes: %d K",
"Physical Memory: %d K (%d K used)",
};
static char *progCaptions[] = {
"Physical Memory Use: %d K",
"Resident Size: %d K",
"Total Size: %d K",
"Resident Mapping Breakdown: %d K",
};
static char *secondLabels[] = {
"gmemusage bug!",
"Private",
"Shared",
"Physical",
"Resident",
};
/*
* static int
* WeWantThisOne(char *progName, long size)
*
* Description:
* Given the name of a program and its size, determine whether to
* display it separately. If we return 1, it will get its own
* bar; if we return 0, it will get lumped in with a bunch of
* others.
*
* The name check occurs if bloatview was started with the -p
* option. Otherwise, just the threshhold is checked.
*
* Parameters:
* progName Name of program under consideration
* size size of program under consideration
*
* Returns:
* 1 if this program is to be displayed separately
* 0 if this program is to be lumped with "other"
*/
static int
WeWantThisOne(char *progName, long size)
{
PROGNAME *name;
if (drawNames) {
name = drawNames;
while (name) {
if (strcmp(progName, name->name) == 0) {
return 1;
}
name = name->next;
}
return 0;
}
return size >= memThresh;
}
/*
* static PROGRAM *
* FirstProgram(PROGRAM *prog)
*
* Description:
* Find the first non-skip program in a list
*
* Parameters:
* prog list of programs
*
* Returns:
* first program in the list that doesn't have the skip flag set
*/
static PROGRAM *
FirstProgram(PROGRAM *prog)
{
while (prog && prog->skip) {
prog = prog->next;
}
return prog;
}
/*
* static PROGRAM *
* NextProgram(PROGRAM *prog)
*
* Description:
* Find the next non-skip program in a list
*
* Parameters:
* prog list of prograsm
*
* Returns:
* the next program in the list that doesn't have the skip
* flag set
*/
static PROGRAM *
NextProgram(PROGRAM *prog)
{
if (!prog) {
return NULL;
}
do {
prog = prog->next;
} while (prog && prog->skip);
return prog;
}
/*
* static PROGRAM *
* PrevProgram(PROGRAM *prog)
*
* Description:
* Find the previous non-skip rogram in a list
*
* Parameters:
* prog list of programs
*
* Returns:
* The previous program in the list that doesn't have the skip
* flag set.
*/
static PROGRAM *
PrevProgram(PROGRAM *prog)
{
if (!prog) {
return NULL;
}
do {
prog = prog->prev;
} while (prog && prog->skip);
return prog;
}
/*
* static void PickVisual(void)
*
* Description:
* Figure out which visual we should use. We are looking for a
* double-buffered visual. We're looking for a TrueColor visual
* with as many pixels as possible to get the real colors we
* want.
*
* If we can't find a TrueColor visual with enough colors, we
* will double-buffer using a pixmap and the default visual
* because we do not want to create our own colormap and have
* colormap flashing.
*/
static void PickVisual(void)
{
int major, minor;
XdbeScreenVisualInfo *info;
int numScreens = 1;
Window root = XDefaultRootWindow(theDpy);
int trueDepth = 0;
Visual *trueVis = NULL;
XVisualInfo *visInfo, visTemplate;
int i, nItems;
theVisual = DefaultVisual(theDpy, DefaultScreen(theDpy));
theDepth = DefaultDepth(theDpy, DefaultScreen(theDpy));
if (useDefaultVisual || !doubleBuffer) {
return;
}
if (!XdbeQueryExtension(theDpy, &major, &minor)) {
return;
}
info = XdbeGetVisualInfo(theDpy, &root, &numScreens);
if (info == NULL) {
return;
}
for (i = 0; i < info->count; i++) {
visTemplate.visualid = info->visinfo[i].visual;
visInfo = XGetVisualInfo(theDpy, VisualIDMask, &visTemplate, &nItems);
if (visInfo->class == TrueColor && visInfo->depth >= 8) {
if (trueVis == NULL || trueDepth < visInfo->depth) {
trueVis = visInfo->visual;
trueDepth = visInfo->depth;
}
usingDBE = 1;
}
}
if (trueVis != NULL) {
assert(usingDBE);
theVisual = trueVis;
theDepth = trueDepth;
}
}
/*
* static int ScaleWidth(int width)
*
* Description:
* Scale a width in our idealized coordinate space (used for the
* layout) into pixels. Scaling is based on the size of the font
* we're using versus the size of our idealized font.
*
* Parameters:
* width size to scale
*
* Returns:
* Scaled width.
*/
static int ScaleWidth(int width)
{
return (width * theFont->max_bounds.width) / FONTWIDTH;
}
/*
* static int ScaleHeight(int width)
*
* Description:
* Scale a height in our idealized coordinate space (used for the
* layout) into pixels. Scaling is based on the size of the font
* we're using versus the size of our idealized font.
*
* Parameters:
* height size to scale
*
* Returns:
* Scaled height.
*/
static int ScaleHeight(int height)
{
return (height * textHeight) / FONTHEIGHT;
}
/*
* static Colormap SetupColors(void)
*
* Description:
* Set up the colors we'll be using.
*
* Returns:
* A Colormap. Other global variables are initialized to the
* proper Pixel values.
*/
static Colormap SetupColors(void)
{
Colormap cmap = 0;
int i;
unsigned long colors[9];
XColor color, exact;
/*
* These colors originated in the IrisGL colormap.
*/
static const char * const colorNames[NUMCOLORS] = {
"#000000", /* black */
"#8e8e38", "#c67171", "#388e83", "#7171c6", /* Bars */
"#71c671", /* Free bar */
"#8e388e", /* Irix bar */
"#00243f", /* Background color */
"#aaaaaa", /* text color */
};
unsigned long white;
if (usingDBE) {
cmap = XCreateColormap(theDpy, DefaultRootWindow(theDpy),
theVisual, AllocNone);
} else {
cmap = DefaultColormap(theDpy, DefaultScreen(theDpy));
}
for (i = 0; i < NUMCOLORS; i++) {
if (!XAllocNamedColor(theDpy, cmap, colorNames[i],
&color, &exact)) {
fprintf(stderr, "Cannot allocate color: %s\n", colorNames[i]);
break;
}
colors[i] = color.pixel;
}
if (i == NUMCOLORS) {
black = colors[0];
for (i = 0; i < 6; i++) {
barColors[i] = colors[i + 1];
}
bgColor = colors[7];
textColor = colors[8];
} else {
/*
* We didn't allocate all of our colors. Fallback to a
* black-and-white color scheme.
*/
if (XAllocNamedColor(theDpy, cmap, "black", &color, &exact)) {
black = color.pixel;
} else {
fprintf(stderr, "Can't allocate black\n");
exit(1);
}
if (XAllocNamedColor(theDpy, cmap, "white", &color, &exact)) {
white = color.pixel;
} else {
fprintf(stderr, "Can't allocate white\n");
exit(1);
}
for (i = 0; i < 6; i++) {
barColors[i] = white;
}
bgColor = white;
textColor = black;
blackAndWhiteMode = 1;
}
return cmap;
}
/*
* This code used to use IrisGL to render. Now it uses X, so that
* users with X terminals can see bloatview. In order to facilitate
* the conversion from IrisGL to X, the following GL functions were
* written in terms of X: color, rectfi, recti, strwidth, cmov2i,
* charstr, swapbuffers, and clear.
*/
/*
* GL color function implemented in X.
*/
static void color(unsigned long index)
{
XSetForeground(theDpy, theGC, index);
}
/*
* static void ConvertGLRectToX(int x1, int y1, int x2, int y2,
* int *x, int *y, int *width, int *height)
*
* Description:
* Convert a GL rectangle to an X rectangle.
*
* Parameters:
* x1 GL rectangle
* y1
* x2
* y2
* x X rectangle.
* y
* width
* height
*/
static void ConvertGLRectToX(int x1, int y1, int x2, int y2,
int *x, int *y, int *width, int *height)
{
if (x1 < x2) {
*x = x1;
*width = x2 - x1;
} else {
*x = x2;
*width = x1 - x2;
}
y1 = winHeight - y1;
y2 = winHeight - y2;
if (y1 < y2) {
*y = y1;
*height = y2 - y1;
} else {
*y = y2;
*height = y1 - y2;
}
}
/*
* GL rectfi function implemented in X.
*/
static void rectfi(int x1, int y1, int x2, int y2)
{
int x, y, width, height;
ConvertGLRectToX(x1, y1, x2, y2, &x, &y, &width, &height);
XFillRectangle(theDpy, backBuffer, theGC, x, y, width, height);
}
/*
* GL recti function implemented in X.
*/
static void recti(int x1, int y1, int x2, int y2)
{
int x, y, width, height;
ConvertGLRectToX(x1, y1, x2, y2, &x, &y, &width, &height);
XDrawRectangle(theDpy, backBuffer, theGC, x, y, width, height);
}
/*
* GL strwidth function implemented in X.
*/
static int strwidth(const char *str)
{
int direction, ascent, descent;
XCharStruct overall;
XTextExtents(theFont, str, strlen(str), &direction, &ascent,
&descent, &overall);
return overall.width;
}
/*
* GL cmov2i function implemented in X.
*/
static void cmov2i(int x, int y)
{
curX = x;
curY = winHeight - y;
}
/*
* GL charstr function implemented in X.
*/
static void charstr(const char *str)
{
XDrawString(theDpy, backBuffer, theGC, curX, curY,
str, strlen(str));
}
/*
* GL swapbuffers function implemented in X.
*/
static void swapbuffers(void)
{
XdbeSwapInfo info;
if (!doubleBuffer) {
return;
}
if (usingDBE) {
info.swap_window = theWin;
info.swap_action = XdbeUndefined;
XdbeSwapBuffers(theDpy, &info, 1);
} else {
XCopyArea(theDpy, backBuffer, theWin, theGC, 0, 0, winWidth,
winHeight, 0, 0);
}
}
/*
* GL clear function implemented in X.
*/
static void clear(void)
{
XFillRectangle(theDpy, backBuffer, theGC, 0, 0, winWidth,
winHeight);
}
/*
* PROGRAM *
* DrawSetup(PROGRAM *new, PROGRAM *old, long physMem, long freeMem,
* BloatType type, SecondType stype, int all, int *barTotal,
* int *numBars)
*
* Description:
* Add a bar for free memory (Physical). Allocate colors
* for each of the bars, add a bar for "other", and add a bar for
* Irix (Physical).
*
* Parameters:
* new The new list of programs to display as bars
* old The last one displayed; we use this to keep the
* colors consistent
* physMem Amount of physical memory on the system
* freeMem Amount of memory the kernel says is free
* type Type of bars to display
* stype Secondary type of bars to display
* all 1 if this is all processes, 0 if this is a breakdown
* for one process
* barTotal gets total for the bar
* numBars gets number of bars
*
* Returns:
* The list of programs to display (same as new with some stuff
* added)
*/
PROGRAM *
DrawSetup(PROGRAM *new, PROGRAM *old, long physMem, long freeMem,
BloatType type, SecondType stype, int all, int *barTotal,
int *numBars)
{
PROGRAM *prev = NULL, *prevColor = NULL, *head;
long progTotal, otherMem = 0, userMem, otherSecondVal = 0;
char otherBuf[100];
int nBars;
nBars = 0;
progTotal = 0;
if (type == Physical || type == MappedObjects) {
if (all) {
*barTotal = physMem;
/*
* Add the free chunk at the top
*/
head = malloc(sizeof *head);
/*
* Be very, very careful here. FreeBloat will free
* progName and mapName, but not manType!!
*/
head->progName = strdup("Free");
head->mapName = strdup("Free");
head->mapType = "Free";
head->value = freeMem;
head->size = head->resSize = head->weightSize = head->privSize
= head->value;
head->secondValue = NOSVAL;
head->color = FREEINDEX;
head->pid = FREEPID;
head->pids = NULL;
head->nProc = 1;
head->skip = 0;
head->special = 1;
head->print = 1;
nBars++;
head->prev = NULL;
head->next = new;
new->prev = head;
} else {
head = new;
}
/*
* Add up all the sizes
*/
while (new) {
progTotal += new->weightSize;
new = new->next;
}
if (!all) {
*barTotal = progTotal;
}
} else {
head = new;
while (new) {
progTotal += type == Resident ? new->resSize : new->size;
new = new->next;
}
*barTotal = progTotal;
}
userMem = progTotal;
new = (type == Physical || type == MappedObjects)
&& all ? head->next : head;
while (new) {
/*
* Set the values for each bar appropriately
*/
switch (type) {
case Physical:
case MappedObjects:
new->value = new->weightSize;
break;
case Resident:
new->value = new->resSize;
break;
default:
case Size:
new->value = new->size;
break;
}
switch (stype) {
default:
case Nostype:
new->secondValue = NOSVAL;
break;
case Priv:
new->secondValue = new->privSize;
break;
case Shared:
new->secondValue = new->weightSize - new->privSize;
break;
case Phys:
new->secondValue = new->weightSize;
break;
case Res:
new->secondValue = new->resSize;
break;
}
/*
* Set the skip flag, allocate colors (using the old list if
* it's valid), and add up the amount to be displayed in the
* "other" bar.
*/
if (!all || WeWantThisOne(new->progName, new->value)) {
new->skip = 0;
new->special = 0;
while (old && old->pid < new->pid) {
old = old->next;
}
if (old && !old->skip && old->pid == new->pid) {
new->color = old->color;
} else if (prevColor) {
new->color = (prevColor->color + 1) % NBARCOLORS;
} else {
new->color = 0;
}
prevColor = new;
nBars++;
} else {
new->skip = 1;
otherMem += new->value;
otherSecondVal += new->secondValue;
}
prev = new;
new = new->next;
}
/*
* Add the other bar
*/
if (otherMem) {
/*
* The check for old resets other color if the rest of the
* bars are also getting reset
*/
if (otherColor == -1 || !old) {
otherColor = prevColor ? (prevColor->color + 1) % NBARCOLORS : 0;
}
new = malloc(sizeof *new);
if (!drawNames) {
snprintf(otherBuf, sizeof otherBuf, "< %d", memThresh);
}
new->progName = strdup(drawNames ? "Other" : otherBuf);
new->mapName = NULL;
new->value = otherMem;
new->secondValue = otherSecondVal;
new->pid = OTHERPID;
new->pids = NULL;
new->color = otherColor;
new->next = NULL;
new->prev = prev;
new->skip = 0;
new->special = 1;
new->print = 0;
new->nProc = 1;
prev->next = new;
prev = new;
nBars++;
}
if ((type == Physical || type == MappedObjects) && all) {
/*
* Add the Irix chunk at the bottom
*/
if (prev) {
new = malloc(sizeof *new);
/*
* Be very, very careful here. FreeBloat will free
* progName and mapName, but not manType!!
*/
new->progName = strdup(IRIX);
new->mapName = strdup(IRIX);
new->mapType = IRIX;
new->value = physMem - userMem - freeMem;
new->size = new->resSize = new->weightSize = new->privSize
= new->value;
new->secondValue = NOSVAL;
new->color = IRIXINDEX;
new->pid = IRIXPID;
new->pids = NULL;
new->nProc = 1;
new->next = NULL;
new->prev = prev;
new->skip = 0;
new->special = 0;
new->print = 1;
prev->next = new;
nBars++;
}
}
*numBars = nBars;
return head;
}
/*
* void SetFontName(const char* fontName)
*
* Description:
* Set the font to use.
*
* Parameters:
* fontName Name of the font to use.
*/
void SetFontName(const char* fontName)
{
theFontName = fontName;
}
/*
* void UseDefaultVisual(void)
*
* Description:
* Use the default visual. This prevents double-buffering.
*/
void UseDefaultVisual(void)
{
useDefaultVisual = 1;
}
/*
* void SetNoDoubleBuffer(void)
*
* Description:
* Disable double-buffering of all kinds.
*/
void SetNoDoubleBuffer(void)
{
doubleBuffer = 0;
}
/*
* void
* Resize(int width, int height)
*
* Description:
* This sets some global variables based on the size of the window.
*/
void
Resize(int width, int height)
{
if (width == winWidth && height == winHeight) {
return;
}
winWidth = width;
winHeight = height;
hmargin = (MARG_NUM * winWidth) / DENOM;
barWidth = (BAR_NUM * winWidth) / DENOM;
if (hmargin * 3 < winHeight) {
topMargin = hmargin;
bottomMargin = (hmargin * 3) / 2;
barHeight = winHeight - topMargin - bottomMargin;
} else {
barHeight = winHeight;
topMargin = bottomMargin = 0;
}
labelLeft = winWidth - ScaleWidth(TEXTWIDTH + SLABELWIDTH);
lineLeft = (winWidth * LINELEFT_NUM) / DENOM;
lineRight = labelLeft - winWidth / DENOM;
labelTop = winHeight - topMargin;
labelBottom = bottomMargin;
if (!usingDBE && doubleBuffer) {
if (backBuffer) {
XFreePixmap(theDpy, backBuffer);
}
backBuffer = XCreatePixmap(theDpy, theWin, winWidth,
winHeight, theDepth);
}
}
/*
* void ShowPrintMode(int mode)
*
* Description:
* Turns print mode (where we print bloat to stdout as well as
* display it graphically on or off. The only affect of calling
* this function is to change string in the title bar.
*
* Parameters:
* mode 0 to turn print mode on, non-zero to turn it off.
*/
void ShowPrintMode(int mode)
{
char title[100], hname[50];
printMode = mode;
if (theDpy != NULL && theWin != 0) {
if (gethostname(hname, sizeof hname) < 0) {
strncpy(title, "gmemusage", sizeof title);
title[sizeof title - 1] = '\0';
} else {
snprintf(title, sizeof title, "gmemusage @ %s", hname);
}
if (mode) {
strncat(title, " (print)", sizeof title - strlen(title));
}
XStoreName(theDpy, theWin, title);
}
}
/*
* void
* Init(int argc, char **argv, Display *dpy, int eventMask,
* PROGNAME *progNames, long threshHold)
*
* Description:
* Initialize. Pick a visual, Create a window, etc.
*
* Parameters:
* argc For setting WM_COMMAND property
* argv For setting WM_COMMAND property
* dpy X Display we're connected to
* eventMask Specifies which X events we get
* progNames List of program names given in -p option
* threshHold the initial threshhold to use (can be changed
*/
void
Init(int argc, char **argv, Display *dpy, int eventMask,
PROGNAME *progNames, long threshHold)
{
XSizeHints *sizeHints;
XClassHint *classHint;
XSetWindowAttributes attr;
XGCValues val;
Colormap cmap;
int minWidth, minHeight, dpyWidth, dpyHeight;;
theDpy = dpy;
PickVisual();
cmap = SetupColors();
attr.background_pixel = bgColor;
attr.border_pixel = bgColor;
attr.colormap = cmap;
attr.event_mask = eventMask;
attr.bit_gravity = NorthWestGravity;
theFont = XLoadQueryFont(theDpy, theFontName);
if (theFont == NULL) {
theFont = XLoadQueryFont(theDpy, "fixed");
if (theFont == NULL) {
theFont = XLoadQueryFont(theDpy, "*");
if (theFont == NULL) {
fprintf(stderr, "Cannot load any fonts!\n");
exit(1);
}
}
}
textHeight = theFont->ascent + theFont->descent;
halfTextHeight = textHeight / 2;
/*
* Scale our size based on the font used for initial layout at
* development time
*/
dpyWidth = DisplayWidth(theDpy, DefaultScreen(theDpy));
dpyHeight = DisplayHeight(theDpy, DefaultScreen(theDpy));
minWidth = ScaleWidth(MINWIDTH);
if (minWidth > dpyWidth) {
minWidth = dpyWidth;
}
minHeight = ScaleHeight(MINHEIGHT);
if (minHeight > dpyHeight) {
minHeight = dpyHeight;
}
theWin = XCreateWindow(dpy, DefaultRootWindow(dpy),
0, 0, minWidth, minHeight, 0,
theDepth, CopyFromParent,
theVisual,
CWBackPixel | CWBorderPixel | CWBitGravity
| CWColormap | CWEventMask, &attr);
XMapWindow(dpy, theWin);
if (!doubleBuffer) {
backBuffer = theWin;
} else if (usingDBE) {
backBuffer = XdbeAllocateBackBufferName(theDpy, theWin,
XdbeUndefined);
} else {
/* backBuffer will get set in Resize(). */
}
sizeHints = XAllocSizeHints();
sizeHints->flags = PMinSize | PMaxSize;
sizeHints->min_width = minWidth;
sizeHints->min_height = minHeight;
sizeHints->max_width = minWidth;
sizeHints->max_height = dpyHeight;
XSetWMNormalHints(dpy, theWin, sizeHints);
XFree(sizeHints);
classHint = XAllocClassHint();
classHint->res_name = "gmemusage";
classHint->res_class = "Gmemusage";
XSetClassHint(theDpy, theWin, classHint);
XFree(classHint);
XSetCommand(theDpy, theWin, argv, argc);
XSetIconName(dpy, theWin, "gmemusage");
ShowPrintMode(printMode);
XSetWMColormapWindows(dpy, theWin, &theWin, 1);
Resize(minWidth, minHeight);
val.font = theFont->fid;
val.graphics_exposures = False;
theGC = XCreateGC(theDpy, backBuffer,
GCFont | GCGraphicsExposures, &val);
memThresh = threshHold;
drawNames = progNames;
}
/*
* static void
* DrawBar(long top, long height, Colorindex barColor, long margin,
* long width, long split)
*
* Description:
* Draw one of the memory bars
*
* Parameters:
* top The top of the bar
* height The height of the bar
* barColor The color of the bar
* margin left side of bar
* width width of bar
* long split
*/
static void
DrawBar(long top, long height, unsigned long barColor, long margin,
long width, long split)
{
color(barColor);
rectfi(margin, top - height, margin + width, top);
color(black);
recti(margin, top, margin + width, top - height);
if (split) {
recti(margin + width - split, top, margin + width, top - height);
}
}
/*
* static void
* SetupLabels(PROGRAM *bloatList, int numBars)
*
* Description:
* Set the labelOffset member of each non-skip element in the
* list. This is kind of tricky. We want each label to be as
* close as possible (vertically) to the center of the bar to
* which it corresponds, while at the same time preventing the
* labels from overlapping.
*
* If there's no extra room, meaning that there are more labels
* than can fit in the space provided, we spread them evenly to
* minimize overlap.
*
* Otherwise, we know that all the labels can fit without
* overlapping, but there may be clusters of labels that are too
* close together. So we locate these clusters, and try to center
* them with respect to the bars to which they correspond. We
* check how much extra space is above and below the cluster, and
* skew either way if there isn't enough for us to center
* ourselves. Furthermore, once we've set up a cluster we have
* to go back up the list, pushing the previous labels up if our
* new cluster location overlaps an old cluster location.
*
* The reason that all this works is that we have predetermined
* that there really is enough space to do this; we always know
* that our cluster will fit one way or the other, and we won't
* push labels up off the top of the window because we checked
* the space first and didn't assume there was more space above
* us than there actually is.
*
* Parameters:
* bloatList list of labels to spread out
* numBars number of bars in the list
*/
static void
SetupLabels(PROGRAM *bloatList, int numBars)
{
int offset;
float aveSep, foffset;
int nLabels, top, bottom, extraAbove, extraBelow;
int nCluster, center, firstCenter, lastCenter;
PROGRAM *bloat, *next, *prev, *elem;
aveSep = (numBars > 1) ?
(float)(labelTop - labelBottom) / (float)(numBars - 1)
: labelTop - labelBottom;
/*
* Spread 'em evenly if there's no extra space. Do the
* calculations in floating point so we utilize the extra space as
* well as we can.
*/
if (aveSep <= textHeight) {
bloat = bloatList;
foffset = labelTop;
while (bloat) {
if (!bloat->skip) {
bloat->labelOffset = foffset;
foffset -= aveSep;
}
bloat = bloat->next;
}
return;
}
/*
* If we got here, then there's enough space to have at least
* textHeight space between each pair of labels. We try our best
* to keep the labels close vertically to the center of the bars
* to which they correspond.
*/
bloat = FirstProgram(bloatList);
nLabels = 0;
while (bloat) {
/*
* Find the extent of this cluster. We consider single labels
* that have lots of space on either side to be clusters of
* size one.
*/
firstCenter = bloat->center;
bottom = firstCenter + halfTextHeight;
next = NextProgram(bloat);
nCluster = 1;
lastCenter = bloat->center;
center = bloat->center;
bottom = bloat->center - textHeight;
while (next && bottom <= next->center) {
nCluster++;
lastCenter = next->center;
center = lastCenter + (firstCenter - lastCenter) / 2;
bottom = center - (nCluster * textHeight) / 2;
next = NextProgram(next);
}
/*
* bottom and center have been set below; now figure out where
* the top is.
*/
top = center + ((nCluster - 1) * textHeight + 1) / 2;
/*
* We want to have this cluster extend from top to bottom.
* Now we calculate how much extra space there is above and
* below us, and skew the cluster down if there's not enough
* space above us and up if there's not enough space below us.
*
* Remember, there's guaranteed to be enough space *somewhere*
* for this cluster to fit.
*/
extraAbove = labelTop - top - textHeight * nLabels;
extraBelow = bottom - labelBottom - textHeight *
(numBars - nLabels - nCluster);
if (extraAbove < 0) {
top += extraAbove;
} else if (extraBelow < 0) {
top -= extraBelow;
}
nLabels += nCluster;
elem = bloat;
offset = top;
/*
* Set the labelOffsets. The members of the cluster will be
* exactly textHeight away from one another.
*/
while (nCluster--) {
elem->labelOffset = offset;
offset -= textHeight;
elem = NextProgram(elem);
}
/*
* Remember where we were for the next iteration
*/
next = elem;
/*
* Push previous labels up if necessary
*/
elem = bloat;
prev = PrevProgram(bloat);
while (prev &&
prev->labelOffset - elem->labelOffset < textHeight) {
prev->labelOffset = elem->labelOffset + textHeight;
elem = prev;
prev = PrevProgram(elem);
}
bloat = next;
}
}
/*
* void
* DrawLabels(PROGRAM *bloatList, SecondType stype)
*
* Description:
* Draw the labels down the right side of the window
*
* Parameters:
* bloatList list of bars and labels
* stype type of secondary labels
*/
void
DrawLabels(PROGRAM *bloatList, SecondType stype)
{
PROGRAM *bloat = bloatList;
char ctype, labelBuf[100];
int value;
if (blackAndWhiteMode) {
color(black);
}
while (bloat) {
if (!bloat->skip) {
if (!blackAndWhiteMode) {
color(barColors[bloat->color]);
}
/*
* Size display resolution if values get too large
*/
if (bloat->value > 999999) {
ctype = 'M';
value = bloat->value / 1024;
} else {
ctype = 'K';
value = bloat->value;
}
/*
* If there is more than one copy of a program running,
* denote that with the number of copies running in
* parentheses.
*/
if (bloat->nProc == 1) {
snprintf(labelBuf, sizeof labelBuf, "%-16.16s: %6d %c",
bloat->progName, value, ctype);
} else if (bloat->nProc < 10) {
snprintf(labelBuf, sizeof labelBuf, "%-12.12s (%d): %6d %c",
bloat->progName, bloat->nProc, value, ctype);
} else {
snprintf(labelBuf, sizeof labelBuf, "%-11.11s (%d): %6d %c",
bloat->progName, bloat->nProc, value, ctype);
}
/*
* For some reason, halfTextHeight / 2 looks really nice.
*/
cmov2i(labelLeft, bloat->labelOffset - halfTextHeight / 2);
charstr(labelBuf);
if (stype != Nostype && bloat->secondValue != NOSVAL) {
if (bloat->secondValue > 999999) {
snprintf(labelBuf, sizeof labelBuf, "%6d M",
bloat->secondValue/1024);
} else {
snprintf(labelBuf, sizeof labelBuf, "%6d K",
bloat->secondValue);
}
cmov2i(winWidth - ScaleWidth(SLABELWIDTH),
bloat->labelOffset - halfTextHeight / 2);
charstr(labelBuf);
}
}
bloat = bloat->next;
}
}
/*
* void
* DrawLines(PROGRAM *bloat)
*
* Description:
* Draw the lines from the bars to the labels
*
* Parameters:
* bloat List of labels and bars
*/
void
DrawLines(PROGRAM *bloat)
{
if (blackAndWhiteMode) {
color(black);
}
while (bloat) {
if (!bloat->skip) {
if (!blackAndWhiteMode) {
color(barColors[bloat->color]);
}
XDrawLine(theDpy, backBuffer, theGC,
lineLeft, winHeight - bloat->center,
lineRight, winHeight - bloat->labelOffset);
}
bloat = bloat->next;
}
}
/*
* void
* Draw(PROGRAM *bloatList, int barTotal, int numBars, char *progName,
* pid_t pid, BloatType type)
*
* Description:
* Draw captions, bars, labels, and lines
*
* Parameters:
* bloatList bars and labels to draw
* barTotal total memory used by bar
* numBars number of bars
* progName name of program (if this is memory use within a program)
* pid process id (if this is memory use within a process)
* type type of chart we're drawing. Used to select the
* proper captions
* stype secondary type of chart, for more captions and labels
*/
void
Draw(PROGRAM *bloatList, int barTotal, int numBars, char *progName,
pid_t pid, BloatType type, SecondType stype)
{
long top, height, split;
int swidth, secondValTotal = 0;
char buf[100], *str, buf2[30];
PROGRAM *bloat;
if (!progName) {
color(bgColor);
clear();
}
/*
* Draw a title based on the type of graph
*/
color(textColor);
if (progName) {
if (pid == -1) {
(void)strcpy(buf2, "all");
} else {
(void)snprintf(buf2, sizeof buf2, "process %d", pid);
}
snprintf(buf, sizeof buf, progTitles[type], progName, buf2);
str = buf;
} else {
str = titles[type];
}
swidth = strwidth(str);
cmov2i(winWidth / 2 - swidth / 2,
winHeight - topMargin / 2 - halfTextHeight);
charstr(str);
/*
* Draw the bars
*/
top = winHeight - topMargin;
bloat = bloatList;
while (bloat) {
if (!bloat->skip) {
height = ((long long)bloat->value * (long long)barHeight)
/ barTotal;
split = bloat->value > 0 ?
(bloat->secondValue * barWidth) / bloat->value : 0;
if (split > barWidth || split < 0) {
split = 0;
}
DrawBar(top, height, barColors[bloat->color],
hmargin, barWidth, split);
bloat->top = top;
bloat->height = height;
bloat->center = top - height / 2;
top -= height;
secondValTotal += bloat->secondValue;
}
bloat = bloat->next;
}
color(textColor);
/*
* Draw the total
*/
if (!progName && (type == Physical || type == MappedObjects)) {
snprintf(buf, sizeof buf, captions[type], barTotal, barTotal -
bloatList->value);
} else {
snprintf(buf, sizeof buf, (progName ? progCaptions : captions)[type],
barTotal);
}
swidth = strwidth(buf);
cmov2i(winWidth / 2 - swidth / 2, textHeight * 2 + halfTextHeight);
charstr(buf);
/*
* Tell the user how to get help
*/
str = progName ?
"Use \"Page Up\" and \"Page Down\" to navigate processes" :
"Press 'h' for help.";
swidth = strwidth(str);
cmov2i(winWidth / 2 - swidth / 2, textHeight);
charstr(str);
/*
* Draw the labels and lines
*/
SetupLabels(bloatList, numBars);
DrawLabels(bloatList, stype);
DrawLines(bloatList);
if (stype != Nostype) {
color(textColor);
cmov2i(winWidth - ScaleWidth(SLABELWIDTH),
winHeight - topMargin / 2 - halfTextHeight);
charstr(secondLabels[stype]);
cmov2i(winWidth - ScaleWidth(SLABELWIDTH),
textHeight * 2 + halfTextHeight);
if (secondValTotal > 999999) {
snprintf(buf, sizeof buf, "%6d M", secondValTotal/1024);
} else {
snprintf(buf, sizeof buf, "%6d K", secondValTotal);
}
charstr(buf);
}
swapbuffers();
}
/*
* void
* DrawShadow(PROGRAM *bloat, int barTotal, char *progName)
*
* Description:
* Draw shadow bars, so the user can tell how a program's memory
* use breakdown fits in with the rest of memory. The current
* program is drawn solid, the rest are just outlines.
*
* Parameters:
* bloat shadow bloat
* barTotal total of the bars
* progName name of the program to draw solid.
*/
void
DrawShadow(PROGRAM *bloat, int barTotal, char *progName)
{
long top, height, left;
color(bgColor);
clear();
color(textColor);
top = winHeight - topMargin - YSHADOWOFF;
left = hmargin - XSHADOWOFF;
while (bloat) {
if (!bloat->skip) {
height = ((long long)bloat->value * (long long)barHeight)
/ barTotal;
(strcmp(bloat->progName, progName) == 0 ?
rectfi : recti)(left, top, left + barWidth, top - height);
bloat->top = top;
bloat->height = height;
top -= height;
}
bloat = bloat->next;
}
/*
* Don't swapbuffers or gflush here; we're depending on a
* subsequent call to Draw for that.
*/
}
/*
* char *
* Select(PROGRAM *bloat, long x, long y, int *procMode, int dragging)
*
* Description:
* The user has clicked the mouse. Figure out what he or she has
* selected.
*
* This function helps implement the policy of what kind of
* display to do by modifying the procMode parameter. What we're
* implementing is that if we're in procMode and the user clicks
* anywhere outside the shadow bar, we should go back to regular
* mode. This may be more convenient than using the space bar,
* which is the "official" means of return.
*
* Parameters:
* bloat List of bloat
* x x coordinate of mouse click
* y y coordinate of mouse click
* procMode 1 if we're in procMode, 0 otherwise
* dragging 1 if we're dragging, 0 otherwise
*
* Returns:
* name of the thing that was clicked on
*/
char *
Select(PROGRAM *bloat, long x, long y, int *procMode, int dragging)
{
y = winHeight - y;
if (*procMode) {
if (y < bottomMargin - YSHADOWOFF
|| y > winHeight - topMargin - YSHADOWOFF) {
if (!dragging) {
*procMode = 0;
}
return NULL;
}
/*
* Adjust x coordinate for procMode. The bars will all have
* the correct top and height for comparison purposes because
* these got calculated in DrawShadow, but the x has to be
* adjusted because the comparison code below assumes the bars
* are drawn in the normal place instead of offset to the
* left.
*/
x += XSHADOWOFF;
}
if (hmargin <= x && x <= hmargin + barWidth) {
while (bloat) {
if (!bloat->skip && !bloat->special && y <= bloat->top
&& y > bloat->top - bloat->height) {
return strdup(bloat->progName);
}
bloat = bloat->next;
}
} else if (!*procMode && labelLeft <= x && x <= winWidth - hmargin) {
while (bloat) {
if (!bloat->skip && !bloat->special && y <=
bloat->labelOffset + halfTextHeight &&
y > bloat->labelOffset - halfTextHeight) {
return strdup(bloat->progName);
}
bloat = bloat->next;
}
} else if (!dragging) {
*procMode = 0;
}
return NULL;
}
/*
* PROGRAM *
* SelectRegion(PROGRAM *bloat, long x, long y)
*
* Description:
* This function gets called when we're looking at the bloat for
* a single process and the user clicks on one of the regions.
* This returns the PROGRAM pointer for that region.
*
* Parameters:
* bloat program bloat for a single process
* x x coordinate of mouse click
* y y coordinate of mouse click
*
* Returns:
* bloat info for the region clicked on, or NULL if no region
* was clicked on.
*/
PROGRAM *
SelectRegion(PROGRAM *bloat, long x, long y)
{
y = winHeight - y;
if (hmargin <= x && x <= hmargin + barWidth) {
while (bloat) {
if (!bloat->skip && !bloat->special && y <= bloat->top
&& y > bloat->top - bloat->height) {
return bloat;
}
bloat = bloat->next;
}
} else if (labelLeft <= x && x <= winWidth - hmargin) {
while (bloat) {
if (!bloat->skip && !bloat->special && y <=
bloat->labelOffset + halfTextHeight &&
y > bloat->labelOffset - halfTextHeight) {
return bloat;
}
bloat = bloat->next;
}
}
return NULL;
}
/*
* void
* Help(void)
*
* Description:
* Display some (hopefully) useful information on running
* bloatview.
*/
void
Help(void)
{
char *str;
int swidth, tabStop;
long textY;
color(bgColor);
clear();
color(textColor);
textY = winHeight - textHeight - halfTextHeight;
str = "Command line usage:";
swidth = strwidth(str);
cmov2i(MINWIDTH / 2 - swidth / 2, textY);
charstr(str);
textY -= textHeight + halfTextHeight;
tabStop = strwidth("gmemusage ");
str = "gmemusage [ -i interval ] [ -m | -p | -r | -s ] [ -u ]";
cmov2i(hmargin, textY);
charstr(str);
textY -= textHeight;
cmov2i(hmargin + tabStop, textY);
charstr("[ -a aiff-file ] [ -g growth-threshhold ] [ -y ]");
textY -= textHeight;
cmov2i(hmargin + tabStop, textY);
charstr("[ -f font ] [ -v ] [ -n ]");
textY -= textHeight;
cmov2i(hmargin + tabStop, textY);
charstr("[ -t thresh ] [ -d delta ] [ progs ... ]");
textY -= textHeight + halfTextHeight;
cmov2i(hmargin, textY);
charstr("interval is the polling interval in milliseconds");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("-m selects Resident Mappings");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("-p selects Physical Memory Breakdown (default)");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("-r selects Resident Sizes of Processes");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("-s selects Total Sizes of Processes");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("-u forces update of inode lookup table");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("aiff-file is the sound for program growth");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("growth-threshhold is the minimum increase for growth sounds");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("-y turns on printing to stdout at each interval");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("font specifies the font to use");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("-v causes gmemusage to use the default visual");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("-n turns off double-buffering");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("thresh is the memory threshhold for displaying");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr(" individual programs (defaults to 500 K),");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("delta is the amount by which the arrow keys");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("change the threshhold (defaults to 50 K), and");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("progs is a list of programs to view.");
textY -= textHeight + halfTextHeight;
str = "Runtime usage:";
swidth = strwidth(str);
cmov2i(MINWIDTH / 2 - swidth / 2, textY);
charstr(str);
textY -= textHeight + halfTextHeight;
cmov2i(hmargin, textY);
charstr("Click on a bar to view detailed memory usage.");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("Press the space bar to return to the overview.");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("'m' selects Resident Mappings");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("'p' selects Physical Memory Breakdown");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("'r' selects Resident Sizes of Processes");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("'s' selects Total Sizes of Processes");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("'v' cycles through some interesting information");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("'t' prints current information to stdout");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("'y' prints current information to stdout every interval");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("up arrow key increases the threshhold");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("down arrow key decreases the threshhold");
textY -= textHeight;
cmov2i(hmargin, textY);
charstr("escape key exits");
textY -= textHeight * 3;
str = "Press the space bar to return to gmemusage Viewer";
swidth = strwidth(str);
cmov2i(MINWIDTH / 2 - swidth / 2, textHeight);
charstr(str);
swapbuffers();
}
/*
* void
* SetThreshHold(long thresh)
*
* Description:
* Specify a new threshhold
*
* Parameters:
* thresh the new threshhold
*/
void
SetThreshHold(long thresh)
{
memThresh = thresh;
}
/*
* void
* WaitMessage(char *message)
*
* Description:
* Display a wait message instead of bars
*
* Parameters:
* message the message to display
* detail another line to display, if not NULL
*/
void
WaitMessage(char *message, char *detail)
{
int swidth;
int textY;
color(bgColor);
clear();
color(textColor);
textY = winHeight / 2 + halfTextHeight;
swidth = strwidth(message);
cmov2i(winWidth / 2 - swidth / 2, textY);
charstr(message);
textY -= textHeight * 2;
if (detail) {
swidth = strwidth(detail);
cmov2i(winWidth / 2 - swidth / 2, textY);
charstr(detail);
}
swapbuffers();
XFlush(theDpy);
}