Reorganisation of the source tree
[ghc-hetmet.git] / utils / heap-view / HpView.lhs
diff --git a/utils/heap-view/HpView.lhs b/utils/heap-view/HpView.lhs
new file mode 100644 (file)
index 0000000..a7b4cbb
--- /dev/null
@@ -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"