summaryrefslogtreecommitdiff
path: root/utils/heap-view/Graph.lhs
blob: b8e08dbb9bc9b63e922fbc0d7176433d1f141b60 (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
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"