/* ** ** Author: Pablo Musa ** Creation Date: mar 27 2011 ** Last Modification: aug 22 2011 ** See Copyright Notice in COPYRIGHT ** ** See vmemory.h for module overview ** */ #include #include #include #include #include #include #include "graphic.h" #include "vmemory.h" #include "lmp_struct.h" #include "lmp.h" #define WINDOW_TITLE "luamemprofiler v1.0" #define ICON_PATH "logo.bmp" /* screen state */ #define LMP_PAUSE 0 #define LMP_EXEC 1 #define LMP_FINISH -1 #define LMP_ZOOM_OUT 0 #define LMP_ZOOM_IN 1 #define MIN_mb_SIZE 1 #define MIN_mb_WIDTH 800 #define MIN_mb_HEIGHT 600 #define BOX_XINI 10 /* MINIMUM = 10 */ #define BOX_YINI 10 /* MINIMUM = 10 */ #define BOX_XEND (mb_width + BOX_XINI) #define BOX_YEND (mb_height + BOX_YINI) #define RTCOLUMN_WIDTH 150 /* right column width */ #define BTROW_HEIGHT 70 /* bottom row height */ #define BASE_SPACE 10 /* space for separating text from the box */ #define BOX_BORDER 3 /* memory box border width */ #define LMP_FLINE 0 /* bottom row starts writing in first line (0) */ #define LMP_ON 1 #define LMP_OFF 0 /* filter type menu - positions to draw, bool toggled, draw Color, and text */ struct menuitem { int x; int y; int toggle; Color color; const char *name; }; typedef struct menuitem LMP_Menuitem; /* GLOBAL VARIABLES */ /* one block list for each filter type */ extern lmp_Block *lmp_string; extern lmp_Block *lmp_function; extern lmp_Block *lmp_userdata; extern lmp_Block *lmp_thread; extern lmp_Block *lmp_table; extern lmp_Block *lmp_other; extern lmp_Block *lmp_all; /* one menu for each filter type */ static LMP_Menuitem mi_string; static LMP_Menuitem mi_function; static LMP_Menuitem mi_userdata; static LMP_Menuitem mi_thread; static LMP_Menuitem mi_table; static LMP_Menuitem mi_other; /* STATIC GLOBAL VARIABLES */ static Screen *screen; static uintptr_t laddress; /* first address of the program */ static uintptr_t baseaddr; /* base address of the memory box */ static int state = LMP_PAUSE; /* luamemprofiler state (paused or executing) */ static int zoom = LMP_ZOOM_OUT; /* zoom state (in or out) */ static int sc_width; /* sc = screen */ static int sc_height; static int mb_width; /* mb = memory box */ static int mb_height; static int BYTES_PER_PIXEL = 4; static int BLOCK_HEIGHT = 2; static int statesy; /* used to update the display with state and zoom */ /* STATIC FUNCTIONS */ static void drawbtrow(); static void toggleall(); static void drawstates(); static void drawmembox(); static void checkevent(); static void clearmembox(); static void untoggleall(); static int istoggled(size_t luatype); static void drawcallstack(int fline); static void drawrtcolumn(float memused); static int setcanvassize (float memused); static void inverttoggle(LMP_Menuitem *mi); static void drawmenuitem(LMP_Menuitem *item); static void drawreport(const char *text, int line); static void drawmemblock(int addr, size_t luatype, size_t size); static void writeblockinfo(void *ptr,size_t luatype,size_t size,int alloctype); static void calcmemdata(void *ptr, size_t size, int *reladdr, size_t *relsize); static void initmenuitem(LMP_Menuitem *mi, int x, int y, int toggle, Color color, const char* name); /* GLOBAL FUNCTIONS */ void vm_start(int lowestaddress, float memused) { /* if memused is very low uses default value and returns it */ memused = setcanvassize (memused); screen = gr_newscreen(sc_width, sc_height, ICON_PATH, WINDOW_TITLE); laddress = lowestaddress; baseaddr = laddress; gr_drawbackground(screen, LMP_VM_BACKGROUND_CL); drawmembox(); drawrtcolumn(memused); drawbtrow(); checkevent(); } void vm_stop() { char dummy; state = LMP_FINISH; drawreport("Execution finished. The report is in the Terminal.", LMP_FLINE); drawstates(); printf("Press Enter To Finish!"); scanf("%c", &dummy); gr_destroyscreen(screen); } void vm_newmemop(int memop, void *ptr, size_t luatype, size_t size) { int p; size_t mb_size; calcmemdata(ptr, size, &p, &mb_size); /* calculate relative address */ if (p > 0) { /* check if is a valid address */ /* draw full block breaking lines if necessary */ if (istoggled(luatype)) drawmemblock(p, luatype, mb_size); if (state == LMP_PAUSE) { /* write in 'bottom row' block information(parameters) and call stack */ writeblockinfo(ptr, luatype, size, memop); } /* check if there is any valid event and treat it */ checkevent(); } /* DEBUG else { possible print if block is smaller than baseaddress printf("Lower Pointer ptr=%d laddr=%d p=%d\n", (int) ptr, laddress, p); } */ } /* STATIC FUNCTIONS */ /* uses baseaddress to calculate the memory box position of a block */ static void calcmemdata(void *ptr, size_t size, int *reladdr, size_t *relsize) { *reladdr = ((uintptr_t) ptr - baseaddr) / BYTES_PER_PIXEL; *relsize = (size / BYTES_PER_PIXEL); if (*relsize == 0) { *relsize = 1; } } /* draw specified text in report area (botton row) */ static void drawreport(const char *text, int line) { int x = BOX_XINI; int y = BOX_YEND + BASE_SPACE + (line * gr_gettextheight(screen)); /* if line does not fit screen size, omit line */ if (y > sc_height - gr_gettextheight(screen)) { return; } /* if line is first line, new message -> clear bottom row */ if (line == LMP_FLINE) { gr_setdrawcolor(screen, LMP_VM_BACKGROUND_CL); gr_drawblock(screen, BOX_XINI, BOX_XEND, BOX_YEND+BASE_SPACE, BTROW_HEIGHT); } /* validate text size */ if (strlen(text) > (mb_width / gr_gettextwidth(screen))) { gr_drawtext(screen, "Sorry, but the text is too large.", x, y); } else { gr_drawtext(screen, text, x, y); } } /* draw up to 3 levels of the call stack */ static void drawcallstack(int fline) { char textbuff[80] = ""; int res, i = 0; lua_Debug ld; lua_State *L = (lua_State *) laddress; res = lua_getstack(L, i, &ld); while (res == 1 && i < (BTROW_HEIGHT/gr_gettextwidth(screen))) { int r; r = lua_getinfo(L, "lnS", &ld); if (r != 0) { int line = LMP_FLINE + i + fline; /* start in fline */ if (strcmp(ld.what, "main") == 0) { /* main program execution */ sprintf(textbuff, "Main - line:%d", ld.currentline); drawreport(textbuff, line); break; } if (strcmp(ld.what, "Lua") == 0) { /* some function execution */ sprintf(textbuff, "Lua - file:'%s' func:'%s' field:'%s' line:%d", ld.short_src, ld.name, ld.namewhat, ld.currentline); drawreport(textbuff, line); } if (strcmp(ld.what, "C") == 0) { /* some C function execution */ sprintf(textbuff, "C - func'%s' field:'%s'", ld.name, ld.namewhat); drawreport(textbuff, line); } } else { printf("luamemprofiler internal error: vmemory -> debuginfo -> invalid what.\n"); exit(EXIT_FAILURE); } i++; res = lua_getstack(L, i, &ld); } } /* draw blocks in the correct place (recursive calls for big blocks) */ static void drawmemblock(int addr, size_t luatype, size_t size) { int x, y; x = (addr % mb_width) + BOX_XINI; y = ((addr / mb_width) * BLOCK_HEIGHT) + BOX_YINI; /* just draw into valid areas */ if (y >= BOX_YINI && (y + BLOCK_HEIGHT) <= BOX_YEND) { if (x + size > BOX_XEND){ /* break block to fit membox */ size_t extrasize = size - (BOX_XEND + 1 - x); /* +1 -> XEND is valid */ size = size - extrasize; /* recursive call for breaking one block in different lines */ drawmemblock(addr + (size - 1), luatype, extrasize); /* -1 -> XEND */ } switch(luatype) { case LUA_TSTRING: gr_setdrawcolor(screen, LMP_VM_STRING_CL); break; case LUA_TFUNCTION: gr_setdrawcolor(screen, LMP_VM_FUNCTION_CL); break; case LUA_TUSERDATA: gr_setdrawcolor(screen, LMP_VM_USERDATA_CL); break; case LUA_TTHREAD: gr_setdrawcolor(screen, LMP_VM_THREAD_CL); break; case LUA_TTABLE: gr_setdrawcolor(screen, LMP_VM_TABLE_CL); break; case LUA_TFREE: gr_setdrawcolor(screen, LMP_VM_FREE_CL); break; default: gr_setdrawcolor(screen, LMP_VM_OTHER_CL); } /* block length is size, so draw from x to "size-1" */ gr_drawblock(screen, x, x + size - 1, y, BLOCK_HEIGHT); } /* DEBUG else { possible use when all blocks have to be drawn printf("draw block (%d, %d) addr = %d\n", x, y, addr); } */ } /* ** traverse a filter list drawing all blocks. 'block' is the first block in the ** list and 'fnextblock' is a function that returns the next block in the list ** or NULL */ void drawblocklist(lmp_Block *block, lmp_Block* (*fnextblock) (lmp_Block*)) { int p; size_t mb_size; while (block != NULL) { /* list is not empty */ calcmemdata(block->ptr, st_getsize(block), &p, &mb_size); if (p > 0) { int luatype = istoggled(block->luatype) ? block->luatype : LUA_TFREE; drawmemblock(p, luatype, mb_size); } block = fnextblock(block); } } /* ** redraw blocks using bigger size and new baseaddress. The new base address is ** calculated using 'y' coordinate. All blocks above this 'y' (y included) are ** redrawn. */ static void zoomin(int x, int y) { int p; size_t mb_size; lmp_Block *block; zoom = LMP_ZOOM_IN; /* change zoom state */ drawstates(); /* update display with new state */ clearmembox(); /* clear memory box for new blocks in zoom mode */ /* calculates baseaddress using old baseaddress and 'y' */ baseaddr = (((y - BOX_YINI) / BLOCK_HEIGHT) * (mb_width) * BYTES_PER_PIXEL) + baseaddr; BYTES_PER_PIXEL = BYTES_PER_PIXEL / 2; /* width 2x bigger */ BLOCK_HEIGHT = BLOCK_HEIGHT * 2; /* height 2x bigger */ for(block = lmp_all; block != NULL; block = st_getnextall(block)) { /* calculates new block values in memry box (relative address and size) */ p = ((uintptr_t) block->ptr - baseaddr) / BYTES_PER_PIXEL; mb_size = (block->size / BYTES_PER_PIXEL); if (mb_size == 0) { mb_size = 1; } /* check if block is inside zoom */ if (istoggled(block->luatype) && p >= 0 && p <= (mb_width * BYTES_PER_PIXEL) * (mb_height / BLOCK_HEIGHT)) { drawmemblock(p, block->luatype, mb_size); } } } /* redraw blocks using smaller size and old baseaddress. */ static void zoomout() { zoom = LMP_ZOOM_OUT; /* change zoom state */ drawstates(); /* update display with new state */ clearmembox(); /* clear memory box for new blocks without zoom mode */ BYTES_PER_PIXEL = BYTES_PER_PIXEL * 2; /* width 2x smaller */ BLOCK_HEIGHT = BLOCK_HEIGHT / 2; /* height 2x smaller */ baseaddr = laddress; /* restore baseaddress */ drawblocklist(lmp_all, st_getnextall); /* draw all blocks */ } static void checkevent() { int eventtype; LMP_Event event; if (state == LMP_EXEC) { /* normal execution - only accepts pause command */ eventtype = gr_getevent(screen, &event); /* gets an event if exists */ if (eventtype == LMP_EVENT_KEY && event.kevent.key == ' ') { /* pause */ state = LMP_PAUSE; drawstates(); /* update display with new state */ drawreport("Press: 'space' to resume execution; 'n' to resume until next memory operation;", LMP_FLINE); drawreport("'c' to clear the memory box; 's,f,u,h,t,o' to redraw blocks of specific type;", LMP_FLINE + 1); drawreport("'a' to redraw all blocks; left-click for zoom in and right-click for zoom out.", LMP_FLINE + 2); } } while (state != LMP_EXEC) { /* execution is paused or finished */ lmp_Block* (*fnextblock) (lmp_Block*) = st_getnexttype; lmp_Block *block = NULL; eventtype = gr_waitevent(screen, &event); /* wait for a valid event */ if (eventtype == LMP_EVENT_KEY) { switch (event.kevent.key) { case ' ': /* space key - continue - resumes normal execution */ state = LMP_EXEC; drawstates(); drawreport("Press 'space' to Pause execution.", LMP_FLINE); return; case 'n': /* next - execute next memory operation */ return; case 's': /* draw filter type - set correct list and toggle */ inverttoggle(&mi_string); block = lmp_string; break; case 'f': inverttoggle(&mi_function); block = lmp_function; break; case 'u': inverttoggle(&mi_userdata); block = lmp_userdata; break; case 'h': inverttoggle(&mi_thread); block = lmp_thread; break; case 't': inverttoggle(&mi_table); block = lmp_table; break; case 'o': inverttoggle(&mi_other); block = lmp_other; break; case 'a': /* draw all blocks */ toggleall(); block = lmp_all; fnextblock = st_getnextall; break; case 'c': /* erase all blocks from memory box */ untoggleall(); clearmembox(); } drawblocklist(block, fnextblock); } else if (eventtype == LMP_EVENT_MOUSE) { if (event.mevent.b == LEFT_BUTTON && zoom == LMP_ZOOM_OUT) { zoomin(event.mevent.x, event.mevent.y); } else if (event.mevent.b == RIGHT_BUTTON && zoom == LMP_ZOOM_IN) { zoomout(); } } } } /* ** write in bottom row the memory operation and the block info. ** block info = allocation type, block (address, type and size) and call stack */ static void writeblockinfo(void *ptr, size_t luatype, size_t size, int alloctype) { char textbuff[60]; char ltype[9]; char atype[8]; switch(luatype) { case LUA_TSTRING: strcpy(ltype, "String"); break; case LUA_TFUNCTION: strcpy(ltype, "Function"); break; case LUA_TUSERDATA: strcpy(ltype, "Userdata"); break; case LUA_TTHREAD: strcpy(ltype, "Thread"); break; case LUA_TTABLE: strcpy(ltype, "Table"); break; default: strcpy(ltype, "Other"); break; } switch(alloctype) { case LMP_VM_FREE: strcpy(atype, "Free"); break; case LMP_VM_MALLOC: strcpy(atype, "Malloc"); break; case LMP_VM_REALLOC: strcpy(atype, "Realloc"); break; } sprintf(textbuff, "%s | addr = %p | type = %s | size = %luB", atype, ptr, ltype, (unsigned long) size); drawreport(textbuff, LMP_FLINE); drawcallstack(LMP_FLINE + 1); } /* draw border lines and clear memory box */ static void drawmembox() { int i; gr_setdrawcolor(screen, BLACK); for (i = 1; i <= BOX_BORDER; i++) { int x1 = BOX_XINI - i, x2 = BOX_XEND + i; int y1 = BOX_YINI - i, y2 = BOX_YEND + i; gr_drawline(screen, x1, y1, x2, y1); gr_drawline(screen, x1, y2, x2, y2); gr_drawline(screen, x1, y1, x1, y2); gr_drawline(screen, x2, y1, x2, y2); } clearmembox(); } /* draw one item of the right column */ static void drawmenuitem(LMP_Menuitem *item) { int offset = gr_gettextwidth(screen) * strlen(item->name) + 3; int bx = item->x + offset; int by = item->y + 4; gr_settextcolor(screen, BLACK); gr_drawtext(screen, item->name, item->x, item->y); if (item->toggle) { gr_setdrawcolor(screen, item->color); gr_drawblock(screen, bx, bx + 10, by, 10); } else { gr_setdrawcolor(screen, LMP_VM_BACKGROUND_CL); gr_drawblock(screen, bx, bx + 10, by, 10); gr_setdrawcolor(screen, item->color); gr_drawline(screen, bx, by, bx + 10, by); gr_drawline(screen, bx, by, bx, by + 10); gr_drawline(screen, bx, by+ 10, bx + 10, by + 10); gr_drawline(screen, bx + 10, by, bx + 10, by + 10); } } /* write a line division(black) and a label(red) in (x,y) coordinate */ static int drawl(const char *text, int x, int y) { int text_width = strlen(text) * gr_gettextwidth(screen); int center_offset = (RTCOLUMN_WIDTH - (text_width + BASE_SPACE))/2; gr_setdrawcolor(screen, BLACK); gr_drawline(screen, x, y, sc_width - BASE_SPACE, y); y = y + 10; gr_settextcolor(screen, RED); gr_drawtext(screen, text, x + center_offset, y); return y; } /* write initial message in the bottom row */ static void drawbtrow() { gr_settextcolor(screen, BLACK); drawreport("Welcome to the luamemprofiler library. Press 'space' to run the program", LMP_FLINE); drawreport("normally or 'n' to execute the program until next memory operation.", LMP_FLINE + 1); } /* draw right column, including initial states (paused, zoom in) */ static void drawrtcolumn(float memused) { int x = BOX_XEND + BOX_BORDER + BASE_SPACE; int y = BOX_YINI; int offset = 30; char textbuff[15]; /* draw basic information */ y = drawl("BASIC INFO", x, y); y = y + offset; gr_settextcolor(screen, BLACK); gr_drawtext(screen, "Memory Size", x, y); y = y + gr_gettextwidth(screen) + 5; sprintf(textbuff, "%.1fMb", memused); gr_drawtext(screen, textbuff, x, y); y = y + offset; gr_drawtext(screen, "Granularity", x, y); y = y + gr_gettextwidth(screen) + 5; sprintf(textbuff, "1x2 px = %dB", BYTES_PER_PIXEL); gr_drawtext(screen, textbuff, x, y); y = y + offset; /* draw key menu and labels */ y = drawl("COMMANDS", x, y); y = y + offset; gr_settextcolor(screen, BLACK); initmenuitem(&mi_string, x, y, LMP_ON, LMP_VM_STRING_CL, "s - String"); drawmenuitem(&mi_string); y = y + offset; initmenuitem(&mi_function, x, y, LMP_ON, LMP_VM_FUNCTION_CL, "f - Function"); drawmenuitem(&mi_function); y = y + offset; initmenuitem(&mi_userdata, x, y, LMP_ON, LMP_VM_USERDATA_CL, "u - Userdata"); drawmenuitem(&mi_userdata); y = y + offset; initmenuitem(&mi_thread, x, y, LMP_ON, LMP_VM_THREAD_CL, "h - Thread"); drawmenuitem(&mi_thread); y = y + offset; initmenuitem(&mi_table, x, y, LMP_ON, LMP_VM_TABLE_CL, "t - Table"); drawmenuitem(&mi_table); y = y + offset; initmenuitem(&mi_other, x, y, LMP_ON, LMP_VM_OTHER_CL, "o - Other"); drawmenuitem(&mi_other); y = y + offset; gr_drawtext(screen, "a - All", x, y); y = y + offset; gr_drawtext(screen, "c - Clear", x, y); y = y + offset; gr_drawtext(screen, "n - Next", x, y); y = y + offset; /* draw luamemprofiler state and zoom state */ y = drawl("STATE", x, y); y = y + offset; statesy = y; /* set where to redraw states */ gr_settextcolor(screen, BLACK); gr_drawtext(screen, "lmp: PAUSED", x, y); y = y + offset; gr_drawtext(screen, "zoom: OUT", x, y); } /* update states (pause x execution || [zoom] in x out */ static void drawstates() { int x = BOX_XEND + BOX_BORDER + BASE_SPACE; int y = statesy; int offset = 30; /* erase old text */ gr_setdrawcolor(screen, LMP_VM_BACKGROUND_CL); gr_drawblock(screen, x, x + RTCOLUMN_WIDTH, y, 50); /* write new text */ gr_settextcolor(screen, BLACK); if(state == LMP_PAUSE) { gr_drawtext(screen, "lmp: PAUSED", x, y); } else if (state == LMP_EXEC) { gr_drawtext(screen, "lmp: EXECUTING", x, y); } else if (state == LMP_FINISH) { gr_drawtext(screen, "lmp: FINISHED", x, y); } y = y + offset; if(zoom == LMP_ZOOM_OUT) { gr_drawtext(screen, "zoom: OUT", x, y); } else if(zoom == LMP_ZOOM_IN) { gr_drawtext(screen, "zoom: IN", x, y); } } /* calculates and sets screen and memory box width and height */ static int setcanvassize (float memused) { if (memused <= (float) MIN_mb_SIZE) { mb_width = MIN_mb_WIDTH; mb_height = MIN_mb_HEIGHT; memused = MIN_mb_SIZE; } else { /* MAX MEM FOR 800 x 600 resolution */ int side; if (memused > 1) { BYTES_PER_PIXEL = ((int) memused) * 4; } /* (1Mb * BLOCK_HEIGHT / BYTES_PER_PIXEL / PROPORTION(4:3)) */ side = (int) sqrt(memused*1000000*BLOCK_HEIGHT/BYTES_PER_PIXEL/(4*3))+1; mb_width = side * 4; mb_height = side * 3; } sc_width = BOX_XINI + mb_width + BOX_BORDER + BASE_SPACE + RTCOLUMN_WIDTH; sc_height = BOX_YINI + mb_height + BOX_BORDER + BASE_SPACE + BTROW_HEIGHT; return memused; } /* paint all memory box with defined color (erase drawn blocks) */ static void clearmembox() { gr_setdrawcolor(screen, LMP_VM_MEMBOX_CL); gr_drawblock(screen, BOX_XINI, BOX_XEND, BOX_YINI, BOX_YEND - BOX_YINI); } static int istoggled(size_t luatype) { switch(luatype) { case LUA_TSTRING: return mi_string.toggle; case LUA_TFUNCTION: return mi_function.toggle; case LUA_TUSERDATA: return mi_userdata.toggle; case LUA_TTHREAD: return mi_thread.toggle; case LUA_TTABLE: return mi_table.toggle; default: return mi_other.toggle; } } static void toggleall() { mi_string.toggle = LMP_ON; drawmenuitem(&mi_string); mi_function.toggle = LMP_ON; drawmenuitem(&mi_function); mi_userdata.toggle = LMP_ON; drawmenuitem(&mi_userdata); mi_thread.toggle = LMP_ON; drawmenuitem(&mi_thread); mi_table.toggle = LMP_ON; drawmenuitem(&mi_table); mi_other.toggle = LMP_ON; drawmenuitem(&mi_other); } static void untoggleall() { mi_string.toggle = LMP_OFF; drawmenuitem(&mi_string); mi_function.toggle = LMP_OFF; drawmenuitem(&mi_function); mi_userdata.toggle = LMP_OFF; drawmenuitem(&mi_userdata); mi_thread.toggle = LMP_OFF; drawmenuitem(&mi_thread); mi_table.toggle = LMP_OFF; drawmenuitem(&mi_table); mi_other.toggle = LMP_OFF; drawmenuitem(&mi_other); } static void initmenuitem(LMP_Menuitem *mi, int x, int y, int toggle, Color color, const char* name) { mi->x = x; mi->y = y; mi->toggle = toggle; mi->color = color; mi->name = name; } /* change toggle settings and redraw menu item */ static void inverttoggle(LMP_Menuitem *mi) { mi->toggle = !mi->toggle; drawmenuitem(mi); }