diff options
Diffstat (limited to 'utils/heap-view')
-rw-r--r-- | utils/heap-view/Graph.lhs | 165 | ||||
-rw-r--r-- | utils/heap-view/HaskXLib.c | 297 | ||||
-rw-r--r-- | utils/heap-view/HpView.lhs | 296 | ||||
-rw-r--r-- | utils/heap-view/HpView2.lhs | 225 | ||||
-rw-r--r-- | utils/heap-view/MAIL | 67 | ||||
-rw-r--r-- | utils/heap-view/Makefile | 31 | ||||
-rw-r--r-- | utils/heap-view/Makefile.original | 48 | ||||
-rw-r--r-- | utils/heap-view/Parse.lhs | 92 | ||||
-rw-r--r-- | utils/heap-view/README | 62 | ||||
-rw-r--r-- | utils/heap-view/common-bits | 35 |
10 files changed, 1318 insertions, 0 deletions
diff --git a/utils/heap-view/Graph.lhs b/utils/heap-view/Graph.lhs new file mode 100644 index 0000000000..b8e08dbb9b --- /dev/null +++ b/utils/heap-view/Graph.lhs @@ -0,0 +1,165 @@ +Started 29/11/93: + +> module Main where +> import PreludeGlaST +> import LibSystem + +Program to draw a graph of last @n@ pieces of data from standard input +continuously. + +> n :: Int +> n = 40 + +> max_sample :: Int +> max_sample = 100 + +> screen_size :: Int +> screen_size = 200 + +Version of grapher that can handle the output of ghc's @+RTS -Sstderr@ +option. + +Nice variant would be to take a list of numbers from the commandline +and display several graphs at once. + +> main :: IO () +> main = +> getArgs >>= \ r -> +> case r of +> [select] -> +> let selection = read select +> in +> xInitialise [] screen_size screen_size >> +> hGetContents stdin >>= \ input -> +> graphloop2 (parseGCData selection input) [] +> _ -> +> error "usage: graph <number in range 0..17>\n" + +The format of glhc18's stderr stuff is: + +-- start of example (view in 120 column window) +graph +RTS -Sstderr -H500 + +Collector: APPEL HeapSize: 500 (bytes) + + Alloc Collect Live Resid GC GC TOT TOT Page Flts No of Roots Caf Mut- Old Collec Resid + bytes bytes bytes ency user elap user elap GC MUT Astk Bstk Reg No able Gen tion %heap + 248 248 60 24.2% 0.00 0.04 0.05 0.23 1 1 1 0 0 1 0 0 Minor +-- end of example + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 + +That is: 6 header lines followed by 17-18 columns of integers, +percentages, floats and text. + +The scaling in the following is largely based on guesses about likely +values - needs tuned. + +@gcParsers@ is a list of functions which parse the corresponding +column and attempts to scale the numbers into the range $0.0 .. 1.0$. +(But may return a number avove $1.0$ which graphing part will scale to +fit screen...) + +(Obvious optimisation - replace by list of scaling information!) + +(Obvious improvement - return (x,y) pair based on elapsed (or user) time.) + +> gcParsers :: [ String -> Float ] +> gcParsers = [ heap, heap, heap, percent, time, time, time, time, flts, flts, stk, stk, reg, caf, caf, heap, text, percent ] +> where +> heap = scale 100000.0 . fromInt . check 0 . readDec +> stk = scale 25000.0 . fromInt . check 0 . readDec +> int = scale 1000.0 . fromInt . check 0 . readDec +> reg = scale 10.0 . fromInt . check 0 . readDec +> caf = scale 100.0 . fromInt . check 0 . readDec +> flts = scale 100.0 . fromInt . check 0 . readDec +> percent = scale 100.0 . check 0.0 . readFloat +> time = scale 20.0 . check 0.0 . readFloat +> text s = 0.0 + +> check :: a -> [(a,String)] -> a +> check error_value parses = +> case parses of +> [] -> error_value +> ((a,s):_) -> a + +> scale :: Float -> Float -> Float +> scale max n = n / max + +> parseGCData :: Int -> String -> [Float] +> parseGCData column input = +> map ((gcParsers !! column) . (!! column) . words) (drop 6 (lines input)) + +Hmmm, how to add logarithmic scaling neatly? Do I still need to? + +Note: unpleasant as it is, the code cannot be simplified to something +like the following. The problem is that the graph won't start to be +drawn until the first @n@ values are available. (Is there also a +danger of clearing the screen while waiting for the next input value?) +A possible alternative solution is to keep count of how many values +have actually been received. + +< graphloop2 :: [Float] -> [Float] -> IO () +< graphloop2 [] = +< return () +< graphloop2 ys = +< let ys' = take n ys +< m = maximum ys' +< y_scale = (floor m) + 1 +< y_scale' = fromInt y_scale +< in +< xCls >> +< drawScales y_scale >> +< draw x_coords [ x / y_scale' | x <- ys' ] >> +< xHandleEvent >> +< graphloop2 (tail ys) + + +> graphloop2 :: [Float] -> [Float] -> IO () +> graphloop2 (y:ys) xs = +> let xs' = take n (y:xs) +> m = maximum xs' +> y_scale = (floor m) + 1 +> y_scale' = fromInt y_scale +> in +> xCls >> +> drawScales y_scale >> +> draw x_coords [ x / y_scale' | x <- xs' ] >> +> xHandleEvent >> +> graphloop2 ys xs' +> graphloop2 [] xs = +> return () + +> x_coords :: [Float] +> x_coords = [ 0.0, 1 / (fromInt n) .. ] + +Draw lines specified by coordinates in range (0.0 .. 1.0) onto screen. + +> draw :: [Float] -> [Float] -> IO () +> draw xs ys = drawPoly (zip xs' (reverse ys')) +> where +> xs' = [ floor (x * sz) | x <- xs ] +> ys' = [ floor ((1.0 - y) * sz) | y <- ys ] +> sz = fromInt screen_size + +> drawPoly :: [(Int, Int)] -> IO () +> drawPoly ((x1,y1):(x2,y2):poly) = +> xDrawLine x1 y1 x2 y2 >> +> drawPoly ((x2,y2):poly) +> drawPoly _ = return () + +Draw horizontal line at major points on y-axis. + +> drawScales :: Int -> IO () +> drawScales y_scale = +> sequence (map drawScale ys) >> +> return () +> where +> ys = [ (fromInt i) / (fromInt y_scale) | i <- [1 .. y_scale - 1] ] + +> drawScale :: Float -> IO () +> drawScale y = +> let y' = floor ((1.0 - y) * (fromInt screen_size)) +> in +> xDrawLine 0 y' screen_size y' + +>#include "common-bits" diff --git a/utils/heap-view/HaskXLib.c b/utils/heap-view/HaskXLib.c new file mode 100644 index 0000000000..b6cf1f137c --- /dev/null +++ b/utils/heap-view/HaskXLib.c @@ -0,0 +1,297 @@ +/*----------------------------------------------------------------------* + * X from Haskell (PicoX) + * + * (c) 1993 Andy Gill + * + *----------------------------------------------------------------------*/ + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#include <stdio.h> +#include <strings.h> + +/*----------------------------------------------------------------------*/ + +/* First the X Globals */ + +Display *MyDisplay; +int MyScreen; +Window MyWindow; +XEvent MyWinEvent; +GC DrawGC; +GC UnDrawGC; + +/* and the Haskell globals */ + +typedef struct { + int HaskButtons[5]; + int HaskPointerX,HaskPointerY; + int PointMoved; +} HaskGlobType; + +HaskGlobType HaskGlob; + +/*----------------------------------------------------------------------*/ + +/* + * Now the access functions into the haskell globals + */ + +int haskGetButtons(int n) +{ + return(HaskGlob.HaskButtons[n]); +} + +int haskGetPointerX(void) +{ + return(HaskGlob.HaskPointerX); +} + +int haskGetPointerY(void) +{ + return(HaskGlob.HaskPointerY); +} + +/*----------------------------------------------------------------------*/ + +/* + *The (rather messy) initiualisation + */ + +haskXBegin(int x,int y,int sty) +{ + /* + * later include these via interface hacks + */ + + /* (int argc, char **argv) */ + int argc = 0; + char **argv = 0; + + XSizeHints XHints; + int MyWinFG, MyWinBG,tmp; + + if ((MyDisplay = XOpenDisplay("")) == NULL) { + fprintf(stderr, "Cannot connect to X server '%s'\n", XDisplayName("")); + exit(1); + } + + MyScreen = DefaultScreen(MyDisplay); + + MyWinBG = WhitePixel(MyDisplay, MyScreen); + MyWinFG = BlackPixel(MyDisplay, MyScreen); + + XHints.x = x; + XHints.y = y; + XHints.width = x; + XHints.height = y; + XHints.flags = PPosition | PSize; + + MyWindow = + XCreateSimpleWindow( + MyDisplay, + DefaultRootWindow(MyDisplay), + x,y, x, y, + 5, + MyWinFG, + MyWinBG + ); + + XSetStandardProperties( + MyDisplay, + MyWindow, + "XLib for Glasgow Haskell", + "XLib for Glasgow Haskell", + None, + argv, + argc, + &XHints + ); + + /* Create drawing and erasing GC */ + + DrawGC = XCreateGC(MyDisplay,MyWindow,0, 0); + XSetBackground(MyDisplay,DrawGC,MyWinBG); + XSetForeground(MyDisplay,DrawGC,MyWinFG); + + UnDrawGC = XCreateGC(MyDisplay,MyWindow,0, 0); + XSetBackground(MyDisplay,UnDrawGC,MyWinFG); + XSetForeground(MyDisplay,UnDrawGC,MyWinBG); + + XSetGraphicsExposures(MyDisplay,DrawGC,False); + XSetGraphicsExposures(MyDisplay,UnDrawGC,False); + XMapRaised(MyDisplay,MyWindow); + + /* the user should be able to choose which are tested for + */ + + XSelectInput( + MyDisplay, + MyWindow, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask + ); + + /* later have more drawing styles + */ + + switch (sty) + { + case 0: + /* Andy, this used to be GXor not much use for Undrawing so I + changed it. (Not much use for colour either - see next + comment */ + XSetFunction(MyDisplay,DrawGC,GXcopy); + XSetFunction(MyDisplay,UnDrawGC,GXcopy); + break; + case 1: + /* Andy, this can have totally bogus results on a colour screen */ + XSetFunction(MyDisplay,DrawGC,GXxor); + XSetFunction(MyDisplay,UnDrawGC,GXxor); + break; + default: + /* Andy, is this really a good error message? */ + printf(stderr,"Wrong Argument to XSet function\n"); + } + /* + * reset the (Haskell) globals + */ + + for(tmp=0;tmp<5;tmp++) + { + HaskGlob.HaskButtons[tmp] = 0; + } + HaskGlob.HaskPointerX = 0; + HaskGlob.HaskPointerY = 0; + HaskGlob.PointMoved = 0; + + XFlush(MyDisplay); + +} + +/*----------------------------------------------------------------------*/ + +/* Boring X ``Do Something'' functions + */ + +haskXClose(void) +{ + XFreeGC( MyDisplay, DrawGC); + XFreeGC( MyDisplay, UnDrawGC); + XDestroyWindow( MyDisplay, MyWindow); + XCloseDisplay( MyDisplay); + return(0); +} + +haskXDraw(x,y,x1,y1) +int x,y,x1,y1; +{ + XDrawLine(MyDisplay, + MyWindow, + DrawGC, + x,y,x1,y1); + return(0); +} + + +haskXPlot(c,x,y) +int c; +int x,y; +{ + XDrawPoint(MyDisplay, + MyWindow, + (c?DrawGC:UnDrawGC), + x,y); + return(0); +} + +haskXFill(c,x,y,w,h) +int c; +int x, y; +int w, h; +{ + XFillRectangle(MyDisplay, + MyWindow, + (c?DrawGC:UnDrawGC), + x, y, w, h); + return(0); +} + +/*----------------------------------------------------------------------*/ + + /* This has to be called every time round the loop, + * it flushed the buffer and handles input from the user + */ + +haskHandleEvent() +{ + XFlush( MyDisplay); + while (XEventsQueued( MyDisplay, QueuedAfterReading) != 0) { + XNextEvent( MyDisplay, &MyWinEvent); + switch (MyWinEvent.type) { + case ButtonPress: + switch (MyWinEvent.xbutton.button) + { + case Button1: HaskGlob.HaskButtons[0] = 1; break; + case Button2: HaskGlob.HaskButtons[1] = 1; break; + case Button3: HaskGlob.HaskButtons[2] = 1; break; + case Button4: HaskGlob.HaskButtons[3] = 1; break; + case Button5: HaskGlob.HaskButtons[4] = 1; break; + } + break; + case ButtonRelease: + switch (MyWinEvent.xbutton.button) + { + case Button1: HaskGlob.HaskButtons[0] = 0; break; + case Button2: HaskGlob.HaskButtons[1] = 0; break; + case Button3: HaskGlob.HaskButtons[2] = 0; break; + case Button4: HaskGlob.HaskButtons[3] = 0; break; + case Button5: HaskGlob.HaskButtons[4] = 0; break; + } + break; + case MotionNotify: + HaskGlob.HaskPointerX = MyWinEvent.xmotion.x; + HaskGlob.HaskPointerY = MyWinEvent.xmotion.y; + HaskGlob.PointMoved = 1; + break; + default: + printf("UNKNOWN INTERUPT ???? (%d) \n",MyWinEvent.type); + break; + } /*switch*/ + } /*if*/ + return(0); +} + + +/*----------------------------------------------------------------------*/ + + /* A function to clear the screen + */ + +haskXCls(void) +{ + XClearWindow(MyDisplay,MyWindow); +} + +/*----------------------------------------------------------------------*/ + + /* A function to write a string + */ + +haskXDrawString(int x,int y,char *str) +{ + return(0); +/* printf("GOT HERE %s %d %d",str,x,y); + XDrawString(MyDisplay,MyWindow,DrawGC,x,y,str,strlen(str)); +*/ +} + +/*----------------------------------------------------------------------*/ + +extern int prog_argc; +extern char **prog_argv; + +haskArgs() +{ + return(prog_argc > 1 ? atoi(prog_argv[1]) : 0); +} diff --git a/utils/heap-view/HpView.lhs b/utils/heap-view/HpView.lhs new file mode 100644 index 0000000000..a7b4cbb78e --- /dev/null +++ b/utils/heap-view/HpView.lhs @@ -0,0 +1,296 @@ +> module Main where +> import PreludeGlaST +> import LibSystem + +> import Parse + +Program to interpret a heap profile. + +Started 28/11/93: parsing of profile +Tweaked 28/11/93: parsing fiddled till it worked and graphical backend added + +To be done: + +0) think about where I want to go with this +1) further processing... sorting, filtering, ... +2) get dynamic display +3) maybe use widgets + +Here's an example heap profile + + JOB "a.out -p" + DATE "Fri Apr 17 11:43:45 1992" + SAMPLE_UNIT "seconds" + VALUE_UNIT "bytes" + BEGIN_SAMPLE 0.00 + SYSTEM 24 + END_SAMPLE 0.00 + BEGIN_SAMPLE 1.00 + elim 180 + insert 24 + intersect 12 + disin 60 + main 12 + reduce 20 + SYSTEM 12 + END_SAMPLE 1.00 + MARK 1.50 + MARK 1.75 + MARK 1.80 + BEGIN_SAMPLE 2.00 + elim 192 + insert 24 + intersect 12 + disin 84 + main 12 + SYSTEM 24 + END_SAMPLE 2.00 + BEGIN_SAMPLE 2.82 + END_SAMPLE 2.82 + +By inspection, the format seems to be: + +profile :== header { sample } +header :== job date { unit } +job :== "JOB" command +date :== "DATE" dte +unit :== "SAMPLE_UNIT" string | "VALUE_UNIT" string + +sample :== samp | mark +samp :== "BEGIN_SAMPLE" time {pairs} "END_SAMPLE" time +pairs :== identifer count +mark :== "MARK" time + +command :== string +dte :== string +time :== float +count :== integer + +But, this doesn't indicate the line structure. The simplest way to do +this is to treat each line as a single token --- for which the +following parser is useful: + +Special purpose parser that recognises a string if it matches a given +prefix and returns the remainder. + +> prefixP :: String -> P String String +> prefixP p = +> itemP `thenP` \ a -> +> let (p',a') = splitAt (length p) a +> in if p == p' +> then unitP a' +> else zeroP + + +To begin with I want to parse a profile into a list of readings for +each identifier at each time. + +> type Sample = (Float, [(String, Int)]) + +> type Line = String + + +> profile :: P Line [Sample] +> profile = +> header `thenP_` +> zeroOrMoreP sample + +> header :: P Line () +> header = +> job `thenP_` +> date `thenP_` +> zeroOrMoreP unit `thenP_` +> unitP () + +> job :: P Line String +> job = prefixP "JOB " + +> date :: P Line String +> date = prefixP "DATE " + +> unit :: P Line String +> unit = +> ( prefixP "SAMPLE_UNIT " ) +> `plusP` +> ( prefixP "VALUE_UNIT " ) + +> sample :: P Line Sample +> sample = +> samp `plusP` mark + +> mark :: P Line Sample +> mark = +> prefixP "MARK " `thenP` \ time -> +> unitP (read time, []) + +ToDo: check that @time1 == time2@ + +> samp :: P Line Sample +> samp = +> prefixP "BEGIN_SAMPLE " `thenP` \ time1 -> +> zeroOrMoreP pair `thenP` \ pairs -> +> prefixP "END_SAMPLE " `thenP` \ time2 -> +> unitP (read time1, pairs) + +> pair :: P Line (String, Int) +> pair = +> prefixP " " `thenP` \ sample_line -> +> let [identifier,count] = words sample_line +> in unitP (identifier, read count) + +This test works fine + +> {- +> test :: String -> String +> test str = ppSamples (theP profile (lines str)) + +> test1 = test example + +> test2 :: String -> Dialogue +> test2 file = +> readFile file exit +> (\ hp -> appendChan stdout (test hp) exit +> done) +> -} + +Inefficient pretty-printer (uses ++ excessively) + +> ppSamples :: [ Sample ] -> String +> ppSamples = unlines . map ppSample + +> ppSample :: Sample -> String +> ppSample (time, samps) = +> (show time) ++ unwords (map ppSamp samps) + +> ppSamp :: (String, Int) -> String +> ppSamp (identifier, count) = identifier ++ ":" ++ show count + +To get the test1 to work in gofer, you need to fiddle with the input +a bit to get over Gofer's lack of string-parsing code. + +> example = +> "JOB \"a.out -p\"\n" ++ +> "DATE \"Fri Apr 17 11:43:45 1992\"\n" ++ +> "SAMPLE_UNIT \"seconds\"\n" ++ +> "VALUE_UNIT \"bytes\"\n" ++ +> "BEGIN_SAMPLE 0.00\n" ++ +> " SYSTEM 24\n" ++ +> "END_SAMPLE 0.00\n" ++ +> "BEGIN_SAMPLE 1.00\n" ++ +> " elim 180\n" ++ +> " insert 24\n" ++ +> " intersect 12\n" ++ +> " disin 60\n" ++ +> " main 12\n" ++ +> " reduce 20\n" ++ +> " SYSTEM 12\n" ++ +> "END_SAMPLE 1.00\n" ++ +> "MARK 1.50\n" ++ +> "MARK 1.75\n" ++ +> "MARK 1.80\n" ++ +> "BEGIN_SAMPLE 2.00\n" ++ +> " elim 192\n" ++ +> " insert 24\n" ++ +> " intersect 12\n" ++ +> " disin 84\n" ++ +> " main 12\n" ++ +> " SYSTEM 24\n" ++ +> "END_SAMPLE 2.00\n" ++ +> "BEGIN_SAMPLE 2.82\n" ++ +> "END_SAMPLE 2.82" + + + + +Hack to let me test this code... Gofer doesn't have integer parsing built in. + +> {- +> read :: String -> Int +> read s = 0 +> -} + +> screen_size = 200 + +ToDo: + +1) the efficiency of finding slices can probably be dramatically + improved... if it matters. + +2) the scaling should probably depend on the slices used + +3) labelling graphs, colour, ... + +4) responding to resize events + +> main :: IO () +> main = +> getArgs >>= \ r -> +> case r of +> filename:idents -> +> readFile filename >>= \ hp -> +> let samples = theP profile (lines hp) +> +> times = [ t | (t,ss) <- samples ] +> names = [ n | (t,ss) <- samples, (n,c) <- ss ] +> counts = [ c | (t,ss) <- samples, (n,c) <- ss ] +> +> time = maximum times +> x_scale = (fromInt screen_size) / time +> +> max_count = maximum counts +> y_scale = (fromInt screen_size) / (fromInt max_count) +> +> slices = map (slice samples) idents +> in +> xInitialise [] screen_size screen_size >> +> -- drawHeap x_scale y_scale samples >> +> sequence (map (drawSlice x_scale y_scale) slices) >> +> freeze +> _ -> error "usage: hpView filename identifiers\n" + +> freeze :: IO () +> freeze = +> xHandleEvent >> +> usleep 100 >> +> freeze + + +Slice drawing stuff... shows profile for each identifier + +> slice :: [Sample] -> String -> [(Float,Int)] +> slice samples ident = +> [ (t,c) | (t,ss) <- samples, c <- [lookupPairs ss ident 0] ] + +> lookupPairs :: Eq a => [(a, b)] -> a -> b -> b +> lookupPairs ((a', b') : hs) a b = +> if a == a' then b' else lookupPairs hs a b +> lookupPairs [] a b = b + +> drawSlice :: Float -> Float -> [(Float,Int)] -> IO () +> drawSlice x_scale y_scale slc = +> drawPoly +> [ (round (x*x_scale), screen_size - (round ((fromInt y)*y_scale))) | (x,y) <- slc ] + +> drawPoly :: [(Int, Int)] -> IO () +> drawPoly ((x1,y1):(x2,y2):poly) = +> xDrawLine x1 y1 x2 y2 >> +> drawPoly ((x2,y2):poly) +> drawPoly _ = return () + + +Very simple heap profiler... doesn't do a proper job at all. Good for +testing. + +> drawHeap :: Float -> Float -> [Sample] -> IO () +> drawHeap x_scale y_scale samples = +> sequence (map xBar +> [ (t*x_scale, (fromInt c)*y_scale) +> | (t,ss) <- samples, (n,c) <- ss ]) >> +> return () + +> xBar :: (Float, Float) -> IO () +> xBar (x, y) = +> let {x' = round x; y' = round y} +> in xDrawLine x' screen_size x' (screen_size - y') + +>#include "common-bits" diff --git a/utils/heap-view/HpView2.lhs b/utils/heap-view/HpView2.lhs new file mode 100644 index 0000000000..fa8044b8b4 --- /dev/null +++ b/utils/heap-view/HpView2.lhs @@ -0,0 +1,225 @@ +> module Main where +> import PreludeGlaST +> import LibSystem + +> import Parse + +Program to do continuous heap profile. + +Bad News: + + The ghc runtime system writes its heap profile information to a + named file (<progname>.hp). The program merrily reads its input + from a named file but has no way of synchronising with the program + generating the file. + +Good News 0: + + You can save the heap profile to a file: + + <progname> <parameters> +RTS -h -i0.1 -RTS + + and then run: + + hpView2 <progname>.hp Main:<functionname> + + This is very like using hp2ps but much more exciting because you + never know what's going to happen next :-) + + +Good News 1: + + The prophet Stallman has blessed us with the shell command @mkfifo@ + (is there a standard Unix version?) which creates a named pipe. If we + instead run: + + mkfifo <progname>.hp + hpView2 <progname>.hp Main:<functionname> & + <progname> <parameters> +RTS -h -i0.1 -RTS + rm <progname>.hp + + Good Things happen. + + NB If you don't delete the pipe, Bad Things happen: the program + writes profiling info to the pipe until the pipe fills up then it + blocks... + + +Right, on with the program: + +Here's an example heap profile + + JOB "a.out -p" + DATE "Fri Apr 17 11:43:45 1992" + SAMPLE_UNIT "seconds" + VALUE_UNIT "bytes" + BEGIN_SAMPLE 0.00 + SYSTEM 24 + END_SAMPLE 0.00 + BEGIN_SAMPLE 1.00 + elim 180 + insert 24 + intersect 12 + disin 60 + main 12 + reduce 20 + SYSTEM 12 + END_SAMPLE 1.00 + MARK 1.50 + MARK 1.75 + MARK 1.80 + BEGIN_SAMPLE 2.00 + elim 192 + insert 24 + intersect 12 + disin 84 + main 12 + SYSTEM 24 + END_SAMPLE 2.00 + BEGIN_SAMPLE 2.82 + END_SAMPLE 2.82 + +In HpView.lhs, I had a fancy parser to handle all this - but it was +immensely inefficient. We can produce something a lot more efficient +and robust very easily by noting that the only lines we care about +have precisely two entries on them. + +> type Line = String +> type Word = String +> type Sample = (Float, [(String, Int)]) + +> parseProfile :: [[Word]] -> [Sample] +> parseProfile [] = [] +> parseProfile ([keyword, time]:lines) | keyword == "BEGIN_SAMPLE" = +> let (sample,rest) = parseSample lines +> in +> (read time, sample) : parseProfile rest +> parseProfile (_:xs) = parseProfile xs + +> parseSample :: [[Word]] -> ([(String,Int)],[[Word]]) +> parseSample ([word, count]:lines) = +> if word == "END_SAMPLE" +> then ([], lines) +> else let (samples, rest) = parseSample lines +> in ( (word, read count):samples, rest ) +> parseSample duff_lines = ([],duff_lines) + +> screen_size = 200 + +> main :: IO () +> main = +> getArgs >>= \ r -> +> case r of +> [filename, ident] -> +> xInitialise [] screen_size screen_size >> +> readFile filename >>= \ hp -> +> let samples = parseProfile (map words (lines hp)) +> totals = [ sum [ s | (_,s) <- ss ] | (t,ss) <- samples ] +> +> ts = map scale totals +> is = map scale (slice samples ident) +> in +> graphloop2 (is, []) (ts, []) +> _ -> error "usage: hpView2 file identifier\n" + +For the example I'm running this on, the following scale does nicely. + +> scale :: Int -> Float +> scale n = (fromInt n) / 10000.0 + +Slice drawing stuff... shows profile for each identifier (Ignores time +info in this version...) + +> slice :: [Sample] -> String -> [Int] +> slice samples ident = +> [ c | (t,ss) <- samples, c <- [lookupPairs ss ident 0] ] + +> lookupPairs :: Eq a => [(a, b)] -> a -> b -> b +> lookupPairs ((a', b') : hs) a b = +> if a == a' then b' else lookupPairs hs a b +> lookupPairs [] a b = b + +Number of samples to display on screen + +> n :: Int +> n = 40 + +Graph-drawing loop. Get's the data for the particular identifier and +the total usage, scales to get total to fit screen and draws them. + +> graphloop2 :: ([Float], [Float]) -> ([Float], [Float]) -> IO () +> graphloop2 (i:is,is') (t:ts, ts') = +> let is'' = take n (i:is') +> ts'' = take n (t:ts') +> +> -- scaling information: +> m = maximum ts'' +> y_scale = (floor m) + 1 +> y_scale' = fromInt y_scale +> in +> xCls >> +> drawScales y_scale >> +> draw x_coords [ x / y_scale' | x <- is'' ] >> +> draw x_coords [ x / y_scale' | x <- ts'' ] >> +> xHandleEvent >> +> graphloop2 (is,is'') (ts, ts'') +> graphloop2 _ _ = +> return () + +> x_coords :: [Float] +> x_coords = [ 0.0, 1 / (fromInt n) .. ] + +Note: unpleasant as it is, the code cannot be simplified to something +like the following (which has scope for changing draw to take a list +of pairs). The problem is that the graph won't start to be drawn +until the first @n@ values are available. (Is there also a danger of +clearing the screen while waiting for the next input value?) A +possible alternative solution is to keep count of how many values have +actually been received. + +< graphloop2 :: [Float] -> [Float] -> IO () +< graphloop2 [] = +< return () +< graphloop2 ys = +< let ys' = take n ys +< m = maximum ys' +< y_scale = (floor m) + 1 +< y_scale' = fromInt y_scale +< in +< xCls >> +< drawScales y_scale >> +< draw x_coords [ x / y_scale' | x <- ys' ] >> +< xHandleEvent >> +< graphloop2 (tail ys) + +Draw lines specified by coordinates in range (0.0 .. 1.0) onto screen. + +> draw :: [Float] -> [Float] -> IO () +> draw xs ys = drawPoly (zip xs' (reverse ys')) +> where +> xs' = [ floor (x * sz) | x <- xs ] +> ys' = [ floor ((1.0 - y) * sz) | y <- ys ] +> sz = fromInt screen_size + +> drawPoly :: [(Int, Int)] -> IO () +> drawPoly ((x1,y1):(x2,y2):poly) = +> xDrawLine x1 y1 x2 y2 >> +> drawPoly ((x2,y2):poly) +> drawPoly _ = return () + +Draw horizontal line at major points on y-axis. + +> drawScales :: Int -> IO () +> drawScales y_scale = +> sequence (map drawScale ys) >> +> return () +> where +> ys = [ (fromInt i) / (fromInt y_scale) | i <- [1 .. y_scale - 1] ] + +> drawScale :: Float -> IO () +> drawScale y = +> let y' = floor ((1.0 - y) * (fromInt screen_size)) +> in +> xDrawLine 0 y' screen_size y' + +>#include "common-bits" diff --git a/utils/heap-view/MAIL b/utils/heap-view/MAIL new file mode 100644 index 0000000000..966fcdcfc7 --- /dev/null +++ b/utils/heap-view/MAIL @@ -0,0 +1,67 @@ +To: partain@dcs.gla.ac.uk +cc: areid@dcs.gla.ac.uk, andy@dcs.gla.ac.uk +Subject: Heap profiling programs +Date: Thu, 09 Dec 93 17:33:09 +0000 +From: Alastair Reid <areid@dcs.gla.ac.uk> + + +I've hacked up a couple of programs which it might be worth putting in +the next ghc distribution. They are: + +graph: + + Draws a continuous graph of any one column of the statistics + produced using the "+RTS -Sstderr" option. + + I'm not convinced this is astonishingly useful since I'm yet to + learn anything useful from (manually) examining these statistics. + (Although I do vaguely remember asking Patrick if the heap profiler + could do stack profiles too.) + + A typical usage is: + + slife 2 Unis/gardenofeden +RTS -Sstderr -H1M -RTS |& graph 2 + + which draws a graph of the third column (ie column 2!) of the + stats. + + (btw is there a neater way of connecting stderr to graph's stdin?) + +hpView2: + + Draws a continuous graph of the statistics reported by the "+RTS -h" + option. + + Since I understand what the figures mean, this seems to be the more + useful program. + + A typical usage is: + + mkfifo slife.hp + hpView2 slife.hp Main:mkQuad & + slife 2 Unis/gardenofeden +RTS -h -i0.1 -RTS + rm slife.hp + + which draws a graph of the total heap usage and the usage for Main:mkQuad. + + +Minor problems: + +The code is a gross hack... but it works. (Maybe distribute in rot13 +format so that you don't get accidentally get exposed to obscene code +:-)) + +The code uses a variant of Andy's picoXlibrary (which he was talking +about releasing but maybe isn't ready to do yet.) + +Also, there are lots of obvious extensions etc which could be made but +haven't yet... (The major one is being able to set the initial +scale-factor for displaying the graphs or being able to graph several +stats at once without having to tee.) + + +Hope you find them interesting. + +Alastair + +ps Code is in ~areid/hask/Life and should be readable/executable. diff --git a/utils/heap-view/Makefile b/utils/heap-view/Makefile new file mode 100644 index 0000000000..e8fa8faf08 --- /dev/null +++ b/utils/heap-view/Makefile @@ -0,0 +1,31 @@ +TOP=../.. +include $(TOP)/mk/boilerplate.mk + +PROGRAMS = graph hpView hpView2 + +SRC_HC_OPTS += -hi-diffs -fglasgow-exts -fhaskell-1.3 -O -L/usr/X11/lib -cpp +SRC_CC_OPTS += -ansi -I/usr/X11/include +# ToDo: use AC_PATH_X in configure to get lib/include dirs for X. + +OBJS_graph = Graph.o HaskXLib.o +OBJS_hpView = HpView.o Parse.o HaskXLib.o +OBJS_hpView2 = HpView2.o Parse.o HaskXLib.o + +all :: $(PROGRAMS) + +graph : $(OBJS_graph) + $(HC) -o $@ $(HC_OPTS) $(LD_OPTS) $(OBJS_graph) -lX11 + +hpView : $(OBJS_hpView) + $(HC) -o $@ $(HC_OPTS) $(LD_OPTS) $(OBJS_hpView) -lX11 + +hpView2 : $(OBJS_hpView2) + $(HC) -o $@ $(HC_OPTS) $(LD_OPTS) $(OBJS_hpView2) -lX11 + +HaskXLib.o : HaskXLib.c + $(CC) -c $(CC_OPTS) HaskXLib.c + +INSTALL_PROGS += $(PROGRAMS) +CLEAN_FILES += $(PROGRAMS) + +include $(TOP)/mk/target.mk diff --git a/utils/heap-view/Makefile.original b/utils/heap-view/Makefile.original new file mode 100644 index 0000000000..1e35bc2e43 --- /dev/null +++ b/utils/heap-view/Makefile.original @@ -0,0 +1,48 @@ +CC=gcc +GLHC18 = glhc18 +GLHC19 = /users/fp/partain/bin/sun4/glhc +HC= ghc -hi-diffs -fglasgow-exts -fhaskell-1.3 +HC_FLAGS = -O -prof -auto-all +#HC_FLAGS = -O +LIBS=-lX11 +FILES2 = Life2.o HaskXLib.o +FILESS = LifeWithStability.o HaskXLib.o +FILES = Life.o HaskXLib.o + +all : hpView hpView2 + +# ADR's heap profile viewer +hpView: HpView.o Parse.o HaskXLib.o + $(HC) -o hpView $(HC_FLAGS) HpView.o Parse.o HaskXLib.o $(LIBS) -L/usr/X11/lib +clean:: + rm -f hpView + +# ADR's continuous heap profile viewer (handles output of -p) +hpView2: HpView2.o Parse.o HaskXLib.o + $(HC) -o hpView2 $(HC_FLAGS) HpView2.o Parse.o HaskXLib.o $(LIBS) -L/usr/X11/lib +clean:: + rm -f hpView2 + + +# ADR's continuous graph program (handles output of -Sstderr) +graph: Graph.o HaskXLib.o + $(HC) -o graph $(HC_FLAGS) Graph.o HaskXLib.o $(LIBS) -L/usr/X11/lib +clean:: + rm -f graph + +# ADR's continuous graph program (part of heap profile viewer) that +# crashes the compiler +bugGraph: bugGraph.o HaskXLib.o + $(HC) -o bugGraph $(HC_FLAGS) bugGraph.o HaskXLib.o $(LIBS) -L/usr/X11/lib +clean:: + rm -f bugGraph + +%.o:%.c + $(CC) -c -ansi -traditional -g -I/usr/X11/include/ $< $(INC) + +%.o:%.lhs + $(HC) $(HC_FLAGS) -c $< $(INC) + +clean:: + rm -f core *.o *% #* + rm -f *.hc diff --git a/utils/heap-view/Parse.lhs b/utils/heap-view/Parse.lhs new file mode 100644 index 0000000000..9d7652fdcc --- /dev/null +++ b/utils/heap-view/Parse.lhs @@ -0,0 +1,92 @@ +> module Parse where + +The Parser monad in "Comprehending Monads" + +> infixr 9 `thenP` +> infixr 9 `thenP_` +> infixr 9 `plusP` + +> type P t a = [t] -> [(a,[t])] + +> unitP :: a -> P t a +> unitP a = \i -> [(a,i)] + +> thenP :: P t a -> (a -> P t b) -> P t b +> m `thenP` k = \i0 -> [(b,i2) | (a,i1) <- m i0, (b,i2) <- k a i1] + +> thenP_ :: P t a -> P t b -> P t b +> m `thenP_` k = \i0 -> [(b,i2) | (a,i1) <- m i0, (b,i2) <- k i1] + +zeroP is the parser that always fails to parse its input + +> zeroP :: P t a +> zeroP = \i -> [] + +plusP combines two parsers in parallel +(called "alt" in "Comprehending Monads") + +> plusP :: P t a -> P t a -> P t a +> a1 `plusP` a2 = \i -> (a1 i) ++ (a2 i) + +itemP is the parser that parses a single token +(called "next" in "Comprehending Monads") + +> itemP :: P t t +> itemP = \i -> [(head i, tail i) | not (null i)] + +force successful parse + +> cutP :: P t a -> P t a +> cutP p = \u -> let l = p u in if null l then [] else [head l] + +find all complete parses of a given string + +> useP :: P t a -> [t] -> [a] +> useP m = \x -> [ a | (a,[]) <- m x ] + +find first complete parse + +> theP :: P t a -> [t] -> a +> theP m = head . (useP m) + + +Some standard parser definitions + +mapP applies f to all current parse trees + +> mapP :: (a -> b) -> P t a -> P t b +> f `mapP` m = m `thenP` (\a -> unitP (f a)) + +filter is the parser that parses a single token if it satisfies a +predicate and fails otherwise. + +> filterP :: (a -> Bool) -> P t a -> P t a +> p `filterP` m = m `thenP` (\a -> (if p a then unitP a else zeroP)) + +lit recognises literals + +> litP :: Eq t => t -> P t () +> litP t = ((==t) `filterP` itemP) `thenP` (\c -> unitP () ) + +> showP :: (Text a) => P t a -> [t] -> String +> showP m xs = show (theP m xs) + + +Simon Peyton Jones adds some useful operations: + +> zeroOrMoreP :: P t a -> P t [a] +> zeroOrMoreP p = oneOrMoreP p `plusP` unitP [] + +> oneOrMoreP :: P t a -> P t [a] +> oneOrMoreP p = seq p +> where seq p = p `thenP` (\a -> +> (seq p `thenP` (\as -> unitP (a:as))) +> `plusP` +> unitP [a] ) + +> oneOrMoreWithSepP :: P t a -> P t b -> P t [a] +> oneOrMoreWithSepP p1 p2 = seq1 p1 p2 +> where seq1 p1 p2 = p1 `thenP` (\a -> seq2 p1 p2 a `plusP` unitP [a]) +> seq2 p1 p2 a = p2 `thenP` (\_ -> +> seq1 p1 p2 `thenP` (\as -> unitP (a:as) )) + diff --git a/utils/heap-view/README b/utils/heap-view/README new file mode 100644 index 0000000000..db9503abc4 --- /dev/null +++ b/utils/heap-view/README @@ -0,0 +1,62 @@ +@HpView.lhs@ is a very primitive heap profile viewer written in +Haskell. It feeds off the same files as hp2ps. It needs a lot of +tidying up and would be far more useful as a continuous display. +(It's in this directory `cos there happens to be a heap profile here +and I couldn't be bothered setting up a new directory, Makefile, etc.) + +@Graph.lhs@ is a continuous heap viewer that "parses" the output of +the +RTS -Sstderr option. Typical usage: + + slife 1 r4 +RTS -Sstderr |& graph 2 + +(You might also try + + cat data | graph 2 + + to see it in action on some sample data. +) + +Things to watch: + + 1) Scaling varies from column to column - consult the source. + + 2) The horizontal scale is not time - it is garbage collections. + + 3) The graph is of the (n+1)st column of the -Sstderr output. + + The data is not always incredibly useful: For example, when using + the (default) Appel 2-space garbage collector, the 3rd column + displays the amount of "live" data in the minor space. A program + with a constant data usage will appear to have a sawtooth usage + as minor data gradually transfers to the major space and then, + suddenly, all gets transferred back at major collections. + Decreasing heap size decreases the size of the minor collections + and increases major collections exaggerating the sawtooth. + + 4) The program is not as robust as it might be. + + +@HpView2.lhs@ is the result of a casual coupling of @Graph.lhs@ and +@HpView.lhs@ which draws continuous graphs of the heap consisting of: +total usage and usage by one particular cost centre. For example: + + mkfifo slife.hp + hpView2 slife.hp Main:mkQuad & + slife 2 Unis/gardenofeden +RTS -h -i0.1 -RTS + rm slife.hp + +draws a graph of total usage and usage by the function @mkQuad@. + +(You might also try + + hpView2 slife.old-hp Main:mkQuad + + to see it in action on some older data) + +The business with named pipes (mkfifo) is a little unfortunate - it +would be nicer if the Haskell runtime system could output to stderr +(say) which I could pipe into hpView which could just graph it's stdin +(like graph does). It's probably worth wrapping the whole thing up in +a little shell-script. + + diff --git a/utils/heap-view/common-bits b/utils/heap-view/common-bits new file mode 100644 index 0000000000..f41223b7f4 --- /dev/null +++ b/utils/heap-view/common-bits @@ -0,0 +1,35 @@ + ----------------------------------------------------------------------------- + + xInitialise :: [String] -> Int -> Int -> IO () + xInitialise str x y = + _ccall_ haskXBegin x y (0::Int) `seqPrimIO` + return () + + xHandleEvent :: IO () + xHandleEvent = + _ccall_ haskHandleEvent `thenPrimIO` \ n -> + case (n::Int) of + 0 -> return () + _ -> error "Unknown Message back from Handle Event" + + xClose :: IO () + xClose = + _ccall_ haskXClose `seqPrimIO` + return () + + xCls :: IO () + xCls = + _ccall_ haskXCls `seqPrimIO` + return () + + xDrawLine :: Int -> Int -> Int -> Int -> IO () + xDrawLine x1 y1 x2 y2 = + _ccall_ haskXDraw x1 y1 x2 y2 `seqPrimIO` + return () + + ---------------------------------------------------------------- + + usleep :: Int -> IO () + usleep t = + _ccall_ usleep t `seqPrimIO` + return () |