From 662a017c1d3c6596bcad4fd2c220889b8676158d Mon Sep 17 00:00:00 2001 From: Jason Evans Date: Wed, 1 Jul 2009 19:24:31 -0700 Subject: [PATCH] Integrate mtrgraph and mtrplay. --- .hgignore | 1 + jemalloc/Makefile.in | 20 +- jemalloc/configure.ac | 24 + jemalloc/src/jemalloc.c | 2 +- jemalloc/src/mtrgraph.c | 1113 +++++++++++++++++++++++++++++++++++++++ jemalloc/src/mtrplay.c | 284 ++++++++++ 6 files changed, 1442 insertions(+), 2 deletions(-) create mode 100644 jemalloc/src/mtrgraph.c create mode 100644 jemalloc/src/mtrplay.c diff --git a/.hgignore b/.hgignore index d173e45b..ef78dd52 100644 --- a/.hgignore +++ b/.hgignore @@ -5,6 +5,7 @@ syntax: regexp ^jemalloc/cfg(hdrs|outputs)\.stamp$ ^jemalloc/config\.(log|status)$ ^jemalloc/configure$ +^jemalloc/doc/jemalloc.3$ ^jemalloc/lib$ ^jemalloc/Makefile$ ^jemalloc/src/jemalloc_defs\.h$ diff --git a/jemalloc/Makefile.in b/jemalloc/Makefile.in index e8d212ea..afcfa711 100644 --- a/jemalloc/Makefile.in +++ b/jemalloc/Makefile.in @@ -11,6 +11,7 @@ SHELL := /bin/sh CC := @CC@ # Configuration parameters. +BINDIR := @BINDIR@ INCLUDEDIR := @INCLUDEDIR@ LIBDIR := @LIBDIR@ MANDIR := @MANDIR@ @@ -23,6 +24,7 @@ CFLAGS += -dynamic endif LDFLAGS := @LDFLAGS@ LIBS := @LIBS@ +GDLIBS := @GDLIBS@ RPATH_EXTRA := @RPATH_EXTRA@ ifeq (macho, @abi@) SO := dylib @@ -32,6 +34,7 @@ endif REV := 0 # File lists. +BINS := @bins@ CHDRS := @srcroot@src/jemalloc.h @objroot@src/jemalloc_defs.h CSRCS := @srcroot@src/jemalloc.c DSOS := @objroot@lib/libjemalloc.so.$(REV) @objroot@lib/libjemalloc.so @@ -40,7 +43,7 @@ MAN3 := @objroot@doc/jemalloc.3 .PHONY: all dist install check clean distclean relclean # Default target. -all: $(DSOS) +all: $(DSOS) bins @objroot@src/%.o: @srcroot@src/%.c $(CC) $(CFLAGS) -c $(CPPFLAGS) -o $@ $+ @@ -50,7 +53,22 @@ $(DSOS): $(CSRCS:@srcroot@%.c=@objroot@%.o) gcc -shared -o $@ $+ $(LDFLAGS) $(LIBS) ln -sf libjemalloc.so.$(REV) lib/libjemalloc.so +bins: $(BINS) + +@objroot@bin/mtrgraph: @objroot@src/mtrgraph.o + @mkdir -p $(@D) + gcc -o $@ $+ $(LDFLAGS) $(LIBS) $(GDLIBS) + +@objroot@bin/%: @objroot@src/%.o + @mkdir -p $(@D) + gcc -o $@ $+ $(LDFLAGS) $(LIBS) + install: + install -d $(BINDIR) + @for b in $(BINS); do \ + echo "install -m 755 $$b $(BINDIR)"; \ + install -m 755 $$b $(BINDIR); \ +done install -d $(INCLUDEDIR) @for h in $(CHDRS); do \ echo "install -m 644 $$h $(INCLUDEDIR)"; \ diff --git a/jemalloc/configure.ac b/jemalloc/configure.ac index 1b59f83c..a37d3cfb 100644 --- a/jemalloc/configure.ac +++ b/jemalloc/configure.ac @@ -541,6 +541,26 @@ if test "x$enable_lazy_lock" = "x1" ; then fi AC_SUBST([enable_lazy_lock]) +dnl ============================================================================ +dnl Configure libgd for mtrgraph. +bins="${objroot}bin/mtrplay" +GDLIBS="" + +have_libgd="yes" +AC_CHECK_HEADERS([gd.h], , [have_libgd="no"]) +AC_CHECK_HEADERS([gdfontt.h], , [have_libgd="no"]) +AC_CHECK_HEADERS([gdfonts.h], , [have_libgd="no"]) +AC_CHECK_HEADERS([gdfontmb.h], , [have_libgd="no"]) +AC_CHECK_HEADERS([gdfontl.h], , [have_libgd="no"]) +AC_CHECK_HEADERS([gdfontg.h], , [have_libgd="no"]) +AC_CHECK_LIB([gd], [gdImageCreate], [GDLIBS="-lgd"], [have_libgd="no"]) + +if test "x${have_libgd}" = "xyes" ; then + bins="${bins} ${objroot}bin/mtrgraph" +fi +AC_SUBST([bins]) +AC_SUBST([GDLIBS]) + dnl ============================================================================ dnl Check for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL @@ -569,6 +589,8 @@ AC_MSG_RESULT([RPATH_EXTRA : ${RPATH_EXTRA}]) AC_MSG_RESULT([]) AC_MSG_RESULT([PREFIX : ${PREFIX}]) AC_MSG_RESULT([BINDIR : ${BINDIR}]) +AC_MSG_RESULT([INCLUDEDIR : ${INCLUDEDIR}]) +AC_MSG_RESULT([LIBDIR : ${LIBDIR}]) AC_MSG_RESULT([DATADIR : ${DATADIR}]) AC_MSG_RESULT([MANDIR : ${MANDIR}]) AC_MSG_RESULT([]) @@ -577,6 +599,8 @@ AC_MSG_RESULT([abs_srcroot : ${abs_srcroot}]) AC_MSG_RESULT([objroot : ${objroot}]) AC_MSG_RESULT([abs_objroot : ${abs_objroot}]) AC_MSG_RESULT([]) +AC_MSG_RESULT([bins : ${bins}]) +AC_MSG_RESULT([]) AC_MSG_RESULT([autogen : ${enable_autogen}]) AC_MSG_RESULT([debug : ${enable_debug}]) AC_MSG_RESULT([stats : ${enable_stats}]) diff --git a/jemalloc/src/jemalloc.c b/jemalloc/src/jemalloc.c index 378d4c87..f7d69df4 100644 --- a/jemalloc/src/jemalloc.c +++ b/jemalloc/src/jemalloc.c @@ -5658,11 +5658,11 @@ void free(void *ptr) { - UTRACE(ptr, 0, 0); if (ptr != NULL) { assert(malloc_initialized); idalloc(ptr); + UTRACE(ptr, 0, 0); } } diff --git a/jemalloc/src/mtrgraph.c b/jemalloc/src/mtrgraph.c new file mode 100644 index 00000000..5dc8f619 --- /dev/null +++ b/jemalloc/src/mtrgraph.c @@ -0,0 +1,1113 @@ +/*- + * Copyright (C) 2009 Facebook, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Facebook, Inc. nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************* + * + * Copyright (C) 2006-2007 Jason Evans . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice(s), this list of conditions and the following disclaimer as + * the first lines of this file unmodified other than the possible + * addition of one or more copyright notices. + * 2. Redistributions in binary form must reproduce the above copyright + * notice(s), this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************* + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "jemalloc_defs.h" +#ifndef JEMALLOC_DEBUG +# define NDEBUG +#endif +#include +#include "rb.h" + +typedef struct +{ + // malloc: {0, size, addr} + // realloc: {oldAddr, size, addr} + // free: {oldAddr, 0, 0} + uintptr_t oldAddr; + size_t size; + uintptr_t addr; +} TraceRecord; + +typedef struct +{ + TraceRecord *trace; + uint64_t traceLen; +} Trace; + +typedef struct AllocationCacheRecordStruct AllocationCacheRecord; +struct AllocationCacheRecordStruct +{ + rb_node(AllocationCacheRecord) link; + + uint64_t addr; + uint64_t size; +}; +typedef rb_tree(AllocationCacheRecord) AllocationCache; + +int +cacheComp(AllocationCacheRecord *a, AllocationCacheRecord *b) +{ + if (a->addr < b->addr) + { + return -1; + } + else if (a->addr > b->addr) + { + return 1; + } + else + { + return 0; + } +} + +rb_wrap(static JEMALLOC_UNUSED, cache_tree_, AllocationCache, + AllocationCacheRecord, link, cacheComp) + +// Parse utrace records. Following are prototypical examples of each type of +// record: +// +// :utrace: 31532 malloc_init() +// :utrace: 31532 0x800b01000 = malloc(34816) +// :utrace: 31532 free(0x800b0a400) +// :utrace: 31532 0x800b35230 = realloc(0x800b35230, 45) +Trace * +parseInput(FILE *infile, uint64_t pastEvent) +{ + Trace *rVal; + uint64_t maxLen; + int result; + char buf[128]; + unsigned pid; + void *addr, *oldAddr; + size_t size; + + rVal = (Trace *) malloc(sizeof(Trace)); + if (rVal == NULL) + { + fprintf(stderr, "mtrgraph: Error in malloc()\n"); + goto RETURN; + } + + maxLen = 1024; + rVal->trace = (TraceRecord *) malloc(maxLen * sizeof(TraceRecord)); + if (rVal->trace == NULL) + { + fprintf(stderr, "mtrgraph: Error in malloc()\n"); + free(rVal); + rVal = NULL; + goto RETURN; + } + + rVal->traceLen = 0; + while (rVal->traceLen < pastEvent) + { + // Expand trace buffer, if necessary. + if (rVal->traceLen == maxLen) + { + TraceRecord *t; + + maxLen *= 2; + t = (TraceRecord *) realloc(rVal->trace, + maxLen * sizeof(TraceRecord)); + if (t == NULL) + { + fprintf(stderr, "mtrgraph: Error in realloc()\n"); + free(rVal->trace); + free(rVal); + rVal = NULL; + goto RETURN; + } + rVal->trace = t; + } + + // Get a line of input. + { + int c; + unsigned i; + + for (i = 0; i < sizeof(buf) - 1; i++) + { + c = fgetc(infile); + switch (c) + { + case EOF: + { + goto RETURN; + } + case '\n': + { + buf[i] = '\0'; + goto OUT; + } + default: + { + buf[i] = c; + break; + } + } + } + OUT:; + } + + // realloc? + result = sscanf(buf, ":utrace: %u %p = realloc(%p, %zu)", + &pid, &addr, &oldAddr, &size); + if (result == 4) + { + rVal->trace[rVal->traceLen].oldAddr = (uintptr_t) oldAddr; + rVal->trace[rVal->traceLen].size = size; + rVal->trace[rVal->traceLen].addr = (uintptr_t) addr; + + rVal->traceLen++; + continue; + } + + // malloc? + result = sscanf(buf, ":utrace: %u %p = malloc(%zu)", + &pid, &addr, &size); + if (result == 3) + { + rVal->trace[rVal->traceLen].oldAddr = (uintptr_t) NULL; + rVal->trace[rVal->traceLen].size = size; + rVal->trace[rVal->traceLen].addr = (uintptr_t) addr; + + rVal->traceLen++; + continue; + } + + // free? + result = sscanf(buf, ":utrace: %u free(%p)", + &pid, &oldAddr); + if (result == 2) + { + rVal->trace[rVal->traceLen].oldAddr = (uintptr_t) oldAddr; + rVal->trace[rVal->traceLen].size = 0; + rVal->trace[rVal->traceLen].addr = (uintptr_t) NULL; + + rVal->traceLen++; + continue; + } + + // malloc_init? + result = sscanf(buf, ":utrace: %u malloc_init()", + &pid); + if (result == 1) + { + continue; + } + + goto ERROR; + } + +RETURN: + return rVal; +ERROR: + fprintf(stderr, "mtrgraph: Error reading record %"PRIu64" of input\n", + rVal->traceLen + 1); + free(rVal->trace); + free(rVal); + rVal = NULL; + goto RETURN; +} + +bool +genOutput(FILE *outfile, const char *fileType, bool legend, + unsigned long xSize, unsigned long ySize, + Trace *trace, uint64_t minAddr, uint64_t maxAddr, uint64_t quantum, + uint64_t minEvent, uint64_t pastEvent, uint64_t stride) +{ + bool rVal; + gdImagePtr img; +#define NCOLORS 256 + int white, black; + int colors[NCOLORS]; + uint64_t i, buckets[ySize]; + unsigned long bucket; + unsigned long x = 0; + AllocationCache cache; + AllocationCacheRecord *rec, key; + gdFontPtr font; + + img = gdImageCreate((int) xSize, (int) ySize); + + black = gdImageColorAllocate(img, 0, 0, 0); + white = gdImageColorAllocate(img, 255, 255, 255); + // Create a palette of colors. + for (i = 0; i < NCOLORS; i++) + { + colors[i] = gdImageColorAllocate(img, 255 - i, i, + (i < NCOLORS / 2) ? i * 2 : (NCOLORS - i - 1) * 2); + } + + // Set up fonts. + font = gdFontGetLarge(); + + memset(buckets, 0, ySize * sizeof(uint64_t)); + + cache_tree_new(&cache); + + for (i = 0; i < trace->traceLen; i++) + { + if (trace->trace[i].oldAddr == 0 && trace->trace[i].addr != 0) + { + // malloc. + + // Update buckets. + if (trace->trace[i].size > 0) + { + uint64_t size, offset; + + size = trace->trace[i].size; + bucket = (trace->trace[i].addr - minAddr) / quantum; + offset = (trace->trace[i].addr - minAddr) % quantum; + + if (bucket < ySize) + { + if (quantum - offset >= size) + { + buckets[bucket] += size; + size = 0; + } + else + { + buckets[bucket] += (quantum - offset); + size -= (quantum - offset); + } + bucket++; + + while (bucket < ySize && size > 0) + { + if (size > quantum) + { + buckets[bucket] += quantum; + size -= quantum; + } + else + { + buckets[bucket] += size; + size = 0; + } + bucket++; + } + } + } + + // Cache size of allocation. + rec = (AllocationCacheRecord *) + malloc(sizeof(AllocationCacheRecord)); + if (rec == NULL) + { + fprintf(stderr, "mtrgraph: Error in malloc()\n"); + rVal = true; + goto RETURN; + } + + rec->addr = trace->trace[i].addr; + rec->size = trace->trace[i].size; + + cache_tree_insert(&cache, rec); + } + else if (trace->trace[i].oldAddr != 0 && trace->trace[i].addr != 0) + { + // realloc. + + // Remove old allocation from cache. + key.addr = trace->trace[i].oldAddr; + rec = cache_tree_search(&cache, &key); + if (rec == NULL) + { + fprintf(stderr, + "mtrgraph: Trace record %"PRIu64 + " realloc()s unknown object 0x%"PRIx64"\n", + i, trace->trace[i].oldAddr); + rVal = true; + goto RETURN; + } + + // Update buckets (dealloc). + if (rec->size > 0) + { + uint64_t size, offset; + + size = rec->size; + bucket = (trace->trace[i].oldAddr - minAddr) / quantum; + offset = (trace->trace[i].oldAddr - minAddr) % quantum; + + if (bucket < ySize) + { + if (quantum - offset >= size) + { + buckets[bucket] -= size; + size = 0; + } + else + { + buckets[bucket] -= (quantum - offset); + size -= (quantum - offset); + } + bucket++; + + while (bucket < ySize && size > 0) + { + if (size > quantum) + { + buckets[bucket] -= quantum; + size -= quantum; + } + else + { + buckets[bucket] -= size; + size = 0; + } + bucket++; + } + } + } + + // Update buckets (alloc). + if (trace->trace[i].size > 0) + { + uint64_t size, offset; + + size = trace->trace[i].size; + bucket = (trace->trace[i].addr - minAddr) / quantum; + offset = (trace->trace[i].addr - minAddr) % quantum; + + if (bucket < ySize) + { + if (quantum - offset >= size) + { + buckets[bucket] += size; + size = 0; + } + else + { + buckets[bucket] += (quantum - offset); + size -= (quantum - offset); + } + bucket++; + + while (bucket < ySize && size > 0) + { + if (size > quantum) + { + buckets[bucket] += quantum; + size -= quantum; + } + else + { + buckets[bucket] += size; + size = 0; + } + bucket++; + } + } + } + + // Cache size of allocation. + cache_tree_remove(&cache, rec); + rec->addr = trace->trace[i].addr; + rec->size = trace->trace[i].size; + cache_tree_insert(&cache, rec); + } + else if (trace->trace[i].oldAddr != 0 + && trace->trace[i].size == 0 + && trace->trace[i].addr == 0) + { + // free. + + // Remove old allocation from cache. + key.addr = trace->trace[i].oldAddr; + rec = cache_tree_search(&cache, &key); + if (rec == NULL) + { + fprintf(stderr, + "mtrgraph: Trace record %"PRIu64 + " free()s unknown object 0x%"PRIx64"\n", + i, trace->trace[i].oldAddr); + rVal = true; + goto RETURN; + } + + // Update buckets. + if (rec->size > 0) + { + uint64_t size, offset; + + size = rec->size; + bucket = (trace->trace[i].oldAddr - minAddr) / quantum; + offset = (trace->trace[i].oldAddr - minAddr) % quantum; + + if (bucket < ySize) + { + if (quantum - offset >= size) + { + buckets[bucket] -= size; + size = 0; + } + else + { + buckets[bucket] -= (quantum - offset); + size -= (quantum - offset); + } + bucket++; + + while (bucket < ySize && size > 0) + { + if (size > quantum) + { + buckets[bucket] -= quantum; + size -= quantum; + } + else + { + buckets[bucket] -= size; + size = 0; + } + bucket++; + } + } + } + + cache_tree_remove(&cache, rec); + free(rec); + } + + // Plot buckets in graph. + if (i >= minEvent && i < pastEvent && ((i - minEvent) % stride) == 0) + { + unsigned long j; + int color; + + for (j = 0; j < ySize; j++) + { + if (buckets[j] > 0) + { + color = (NCOLORS * ((double) buckets[j] / (double) quantum)) + - 1; + gdImageSetPixel(img, x, ySize - j, colors[color]); + } + } + x++; + } + } + + // Print graph legend. + if (legend) + { +#define BUFLEN 256 + char buf[BUFLEN]; + + // Create color palette legend. + if (ySize >= NCOLORS) + { + for (i = 0; i < NCOLORS; i++) + { + gdImageLine(img, 0, NCOLORS - i - 1, 31, NCOLORS - i - 1, + colors[i]); + } + gdImageLine(img, 0, 0, 31, 0, white); + gdImageLine(img, 0, 256, 31, 256, white); + gdImageLine(img, 0, 0, 0, 256, white); + gdImageLine(img, 31, 0, 31, 256, white); + + gdImageString(img, font, 40, 0, (unsigned char *)"Full bucket", + white); + gdImageString(img, font, 40, 240, + (unsigned char *)"Fragmented bucket", white); + } + + snprintf(buf, BUFLEN, + "Horizontal: Events [%"PRIu64"..%"PRIu64"), stride %"PRIu64"", + minEvent, pastEvent, stride); + gdImageString(img, font, 200, 0, (unsigned char *)buf, white); + + snprintf(buf, BUFLEN, + "Vertical: Addresses [0x%016"PRIx64"..0x%016"PRIx64 + "), bucket size %"PRIu64"", minAddr, maxAddr, quantum); + gdImageString(img, font, 200, 20, (unsigned char *)buf, white); + + snprintf(buf, BUFLEN, + "Graph dimensions: %lu events by %lu buckets", + xSize, ySize); + gdImageString(img, font, 200, 40, (unsigned char *)buf, white); + } + + if (strcmp(fileType, "png") == 0) + { + gdImagePng(img, outfile); + } + else if (strcmp(fileType, "jpg") == 0) + { + gdImageJpeg(img, outfile, 100); + } + else if (strcmp(fileType, "gif") == 0) + { + gdImageGif(img, outfile); + } + else + { + // Unreachable code. + fprintf(stderr, "mtrgraph: Unsupported output file type '%s'\n", + fileType); + rVal = true; + goto RETURN; + } + + rVal = false; +RETURN: + // Clean up cache. + while (true) + { + rec = cache_tree_first(&cache); + if (rec == NULL) + { + break; + } + + cache_tree_remove(&cache, rec); + free(rec); + } + gdImageDestroy(img); + return rVal; +} + +void +usage(FILE *fp) +{ + fprintf(fp, "mtrgraph usage:\n"); + fprintf(fp, " mtrgraph -h\n"); + fprintf(fp, " mtrgraph []\n"); + fprintf(fp, "\n"); + fprintf(fp, " Option | Description\n"); + fprintf(fp, " -----------+------------------------------------------\n"); + fprintf(fp, " -h | Print usage and exit.\n"); + fprintf(fp, " -n | Don't actually generate a graph.\n"); + fprintf(fp, " -q | Don't print statistics to stdout.\n"); + fprintf(fp, " -l | Don't generate legend in graph.\n"); + fprintf(fp, " -f | Input filename.\n"); + fprintf(fp, " -o | Output filename.\n"); + fprintf(fp, " -t | Output file type (png*, gif, jpg).\n"); + fprintf(fp, " -x | Horizontal size of graph area, in pixels.\n"); + fprintf(fp, " -y | Vertical size of graph area, in pixels.\n"); + fprintf(fp, " -m | Minimum address to graph.\n"); + fprintf(fp, " -M | Maximum address to graph.\n"); + fprintf(fp, " -e