summaryrefslogtreecommitdiff
path: root/ghc/utils/heap-view/HpView.lhs
blob: a7b4cbb78ead5a2a74cd69acc7a99c0bee54c7a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
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"