7 Program to do continuous heap profile.
11 The ghc runtime system writes its heap profile information to a
12 named file (<progname>.hp). The program merrily reads its input
13 from a named file but has no way of synchronising with the program
18 You can save the heap profile to a file:
20 <progname> <parameters> +RTS -h -i0.1 -RTS
24 hpView2 <progname>.hp Main:<functionname>
26 This is very like using hp2ps but much more exciting because you
27 never know what's going to happen next :-)
32 The prophet Stallman has blessed us with the shell command @mkfifo@
33 (is there a standard Unix version?) which creates a named pipe. If we
37 hpView2 <progname>.hp Main:<functionname> &
38 <progname> <parameters> +RTS -h -i0.1 -RTS
43 NB If you don't delete the pipe, Bad Things happen: the program
44 writes profiling info to the pipe until the pipe fills up then it
48 Right, on with the program:
50 Here's an example heap profile
53 DATE "Fri Apr 17 11:43:45 1992"
82 In HpView.lhs, I had a fancy parser to handle all this - but it was
83 immensely inefficient. We can produce something a lot more efficient
84 and robust very easily by noting that the only lines we care about
85 have precisely two entries on them.
89 > type Sample = (Float, [(String, Int)])
91 > parseProfile :: [[Word]] -> [Sample]
92 > parseProfile [] = []
93 > parseProfile ([keyword, time]:lines) | keyword == "BEGIN_SAMPLE" =
94 > let (sample,rest) = parseSample lines
96 > (read time, sample) : parseProfile rest
97 > parseProfile (_:xs) = parseProfile xs
99 > parseSample :: [[Word]] -> ([(String,Int)],[[Word]])
100 > parseSample ([word, count]:lines) =
101 > if word == "END_SAMPLE"
103 > else let (samples, rest) = parseSample lines
104 > in ( (word, read count):samples, rest )
105 > parseSample duff_lines = ([],duff_lines)
113 > [filename, ident] ->
114 > xInitialise [] screen_size screen_size >>
115 > readFile filename >>= \ hp ->
116 > let samples = parseProfile (map words (lines hp))
117 > totals = [ sum [ s | (_,s) <- ss ] | (t,ss) <- samples ]
119 > ts = map scale totals
120 > is = map scale (slice samples ident)
122 > graphloop2 (is, []) (ts, [])
123 > _ -> error "usage: hpView2 file identifier\n"
125 For the example I'm running this on, the following scale does nicely.
127 > scale :: Int -> Float
128 > scale n = (fromInt n) / 10000.0
130 Slice drawing stuff... shows profile for each identifier (Ignores time
131 info in this version...)
133 > slice :: [Sample] -> String -> [Int]
134 > slice samples ident =
135 > [ c | (t,ss) <- samples, c <- [lookupPairs ss ident 0] ]
137 > lookupPairs :: Eq a => [(a, b)] -> a -> b -> b
138 > lookupPairs ((a', b') : hs) a b =
139 > if a == a' then b' else lookupPairs hs a b
140 > lookupPairs [] a b = b
142 Number of samples to display on screen
147 Graph-drawing loop. Get's the data for the particular identifier and
148 the total usage, scales to get total to fit screen and draws them.
150 > graphloop2 :: ([Float], [Float]) -> ([Float], [Float]) -> IO ()
151 > graphloop2 (i:is,is') (t:ts, ts') =
152 > let is'' = take n (i:is')
153 > ts'' = take n (t:ts')
155 > -- scaling information:
157 > y_scale = (floor m) + 1
158 > y_scale' = fromInt y_scale
161 > drawScales y_scale >>
162 > draw x_coords [ x / y_scale' | x <- is'' ] >>
163 > draw x_coords [ x / y_scale' | x <- ts'' ] >>
165 > graphloop2 (is,is'') (ts, ts'')
169 > x_coords :: [Float]
170 > x_coords = [ 0.0, 1 / (fromInt n) .. ]
172 Note: unpleasant as it is, the code cannot be simplified to something
173 like the following (which has scope for changing draw to take a list
174 of pairs). The problem is that the graph won't start to be drawn
175 until the first @n@ values are available. (Is there also a danger of
176 clearing the screen while waiting for the next input value?) A
177 possible alternative solution is to keep count of how many values have
178 actually been received.
180 < graphloop2 :: [Float] -> [Float] -> IO ()
184 < let ys' = take n ys
186 < y_scale = (floor m) + 1
187 < y_scale' = fromInt y_scale
190 < drawScales y_scale >>
191 < draw x_coords [ x / y_scale' | x <- ys' ] >>
193 < graphloop2 (tail ys)
195 Draw lines specified by coordinates in range (0.0 .. 1.0) onto screen.
197 > draw :: [Float] -> [Float] -> IO ()
198 > draw xs ys = drawPoly (zip xs' (reverse ys'))
200 > xs' = [ floor (x * sz) | x <- xs ]
201 > ys' = [ floor ((1.0 - y) * sz) | y <- ys ]
202 > sz = fromInt screen_size
204 > drawPoly :: [(Int, Int)] -> IO ()
205 > drawPoly ((x1,y1):(x2,y2):poly) =
206 > xDrawLine x1 y1 x2 y2 >>
207 > drawPoly ((x2,y2):poly)
208 > drawPoly _ = return ()
210 Draw horizontal line at major points on y-axis.
212 > drawScales :: Int -> IO ()
213 > drawScales y_scale =
214 > sequence (map drawScale ys) >>
217 > ys = [ (fromInt i) / (fromInt y_scale) | i <- [1 .. y_scale - 1] ]
219 > drawScale :: Float -> IO ()
221 > let y' = floor ((1.0 - y) * (fromInt screen_size))
223 > xDrawLine 0 y' screen_size y'
225 >#include "common-bits"