summaryrefslogtreecommitdiff
path: root/utils/heap-view/HpView.lhs
diff options
context:
space:
mode:
Diffstat (limited to 'utils/heap-view/HpView.lhs')
-rw-r--r--utils/heap-view/HpView.lhs296
1 files changed, 296 insertions, 0 deletions
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"