/* * draw.c * * Drawing bloat */ #include #include #include #include #include #include #include #include #include #include #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); }