/*- * 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. */ // Compile as: // // gcc -g -O mtrgraph.c -o mtrgraph -I/usr/local/include -L/usr/local/lib -lgd #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #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; } } // Parse kdump records. Following are prototypical examples of each type of // record: // // 31532 vim USER malloc_init() // 31532 vim USER 0x800b01000 = malloc(34816) // 31532 vim USER free(0x800b0a400) // 31532 vim USER 0x800b35230 = realloc(0x800b35230, 45) Trace * parseInput(FILE *infile, uint64_t pastEvent) { Trace *rVal; uint64_t maxLen; int result; char buf[128]; unsigned pid; char argv0[16]; 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, "%u %15s USER %p = realloc(%p, %zu)", &pid, argv0, &addr, &oldAddr, &size); if (result == 5) { 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, "%u %15s USER %p = malloc(%zu)", &pid, argv0, &addr, &size); if (result == 4) { 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, "%u %15s USER free(%p)", &pid, argv0, &oldAddr); if (result == 3) { 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, "%u %15s USER malloc_init()", &pid, argv0); if (result == 2) { continue; } goto ERROR; } RETURN: return rVal; ERROR: fprintf(stderr, "mtrgraph: Error reading record %llu 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)); rb_tree_new(&cache, link); 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; } rb_node_new(&cache, rec, link); rec->addr = trace->trace[i].addr; rec->size = trace->trace[i].size; rb_insert(&cache, rec, cacheComp, AllocationCacheRecord, link); } else if (trace->trace[i].oldAddr != 0 && trace->trace[i].addr != 0) { // realloc. // Remove old allocation from cache. key.addr = trace->trace[i].oldAddr; rb_search(&cache, &key, cacheComp, link, rec); if (rec == rb_tree_nil(&cache)) { fprintf(stderr, "mtrgraph: Trace record %llu realloc()s unknown object" " %p\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. rb_remove(&cache, rec, AllocationCacheRecord, link); rec->addr = trace->trace[i].addr; rec->size = trace->trace[i].size; rb_insert(&cache, rec, cacheComp, AllocationCacheRecord, link); } 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; rb_search(&cache, &key, cacheComp, link, rec); if (rec == rb_tree_nil(&cache)) { fprintf(stderr, "mtrgraph: Trace record %llu free()s unknown object" " %p\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++; } } } rb_remove(&cache, rec, AllocationCacheRecord, link); 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, "Full bucket", white); gdImageString(img, font, 40, 240, "Fragmented bucket", white); } snprintf(buf, BUFLEN, "Horizontal: Events [%llu..%llu), stride %llu", minEvent, pastEvent, stride); gdImageString(img, font, 200, 0, buf, white); snprintf(buf, BUFLEN, "Vertical: Addresses [0x%016llx..0x%016llx), bucket size %llu", minAddr, maxAddr, quantum); gdImageString(img, font, 200, 20, buf, white); snprintf(buf, BUFLEN, "Graph dimensions: %lu events by %lu buckets", xSize, ySize); gdImageString(img, font, 200, 40, 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) { rb_first(&cache, link, rec); if (rec == rb_tree_nil(&cache)) { break; } rb_remove(&cache, rec, AllocationCacheRecord, link); 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