1 -----------------------------------------------------------------------------
2 -- $Id: Main.hs,v 1.2 2000/03/01 18:38:45 keithw Exp $
4 -- (c) Simon Marlow 1997-1999
5 -----------------------------------------------------------------------------
25 -----------------------------------------------------------------------------
29 die s = hPutStr stderr s >> exitWith (ExitFailure 1)
31 usageHeader = "usage: nofib-analyse [OPTION...] <logfile1> <logfile2> ..."
35 if not (null cmdline_errors) || OptHelp `elem` flags
36 then die (concat cmdline_errors ++ usageInfo usageHeader argInfo)
39 let { html = OptHTMLOutput `elem` flags;
40 ascii = OptASCIIOutput `elem` flags
44 then die "Can't produce both ASCII and HTML"
48 then die "Can't both display and hide deviations"
51 results <- parse_logs other_args
53 let column_headings = map (reverse . takeWhile (/= '/') . reverse) other_args
56 then putStr (renderHtml (htmlPage results column_headings))
57 else putStr (asciiPage results column_headings)
60 parse_logs :: [String] -> IO [ResultTable]
62 f <- hGetContents stdin
64 parse_logs log_files =
65 mapM (\f -> do h <- openFile f ReadMode
67 return (parse_log c)) log_files
69 -----------------------------------------------------------------------------
70 -- List of tables we're going to generate
72 data PerProgTableSpec =
73 forall a . Result a =>
75 String -- Name of the table
76 String -- HTML tag for the table
77 (Results -> Maybe a) -- How to get the result
78 (Results -> Status) -- How to get the status of this result
79 (a -> Bool) -- Result within reasonable limits?
81 data PerModuleTableSpec =
82 forall a . Result a =>
84 String -- Name of the table
85 String -- HTML tag for the table
86 (Results -> FiniteMap String a) -- get the module map
87 (a -> Bool) -- Result within reasonable limits?
90 [ SpecP "Binary Sizes" "binary-sizes" binary_size compile_status always_ok
91 , SpecP "Allocations" "allocations" allocs run_status always_ok
92 , SpecP "Run Time" "run-times" run_time run_status time_ok
93 , SpecP "Mutator Time" "mutator-time" mut_time run_status time_ok
94 , SpecP "GC Time" "gc-time" gc_time run_status time_ok
95 , SpecP "GC Work" "gc-work" gc_work run_status always_ok
96 , SpecP "Instructions" "instrs" instrs run_status always_ok
97 , SpecP "Memory Reads" "mem-reads" mem_reads run_status always_ok
98 , SpecP "Memory Writes" "mem-writes" mem_writes run_status always_ok
101 per_module_result_tab =
102 [ SpecM "Module Sizes" "mod-sizes" module_size always_ok
103 , SpecM "Compile Times" "compile-time" compile_time time_ok
106 always_ok :: a -> Bool
107 always_ok = const True
109 time_ok :: Float -> Bool
110 time_ok t = t > tooquick_threshold
112 -----------------------------------------------------------------------------
113 -- HTML page generation
115 htmlPage results args
116 = header [] (theTitle [] (htmlStr "NoFib Results"))
120 +++ body [] (gen_tables results args)
122 gen_menu = ul [] (foldr1 (+++) (map (li [] +++)
123 (map (prog_menu_item) per_prog_result_tab
124 ++ map (module_menu_item) per_module_result_tab)))
126 prog_menu_item (SpecP name anc _ _ _) = anchor [href ('#':anc)] (htmlStr name)
127 module_menu_item (SpecM name anc _ _) = anchor [href ('#':anc)] (htmlStr name)
129 gen_tables results args =
130 foldr1 (+++) (map (htmlGenProgTable results args) per_prog_result_tab)
131 +++ foldr1 (+++) (map (htmlGenModTable results args) per_module_result_tab)
133 htmlGenProgTable results args (SpecP title anc get_result get_status result_ok)
134 = sectHeading title anc
136 mkTable (htmlShowResults results args get_result get_status result_ok))
139 htmlGenModTable results args (SpecM title anc get_result result_ok)
140 = sectHeading title anc
142 mkTable (htmlShowMultiResults results args get_result result_ok))
145 sectHeading :: String -> String -> Html
147 = h2 [] (anchor [name nm] (htmlStr s))
153 -> (Results -> Maybe a)
154 -> (Results -> Status)
158 htmlShowResults (r:rs) ss f stat result_ok
160 +/+ foldr1 (+/+) (zipWith tableRow [1..] results_per_prog)
161 +/+ foldr1 (+/+) (tableRow (-1) ("Average", gms)
163 else [tableRow (-1) ("-1 s.d.", lows),
164 tableRow (-1) ("+1 s.d.", highs)])
166 -- results_per_prog :: [ (String,[BoxValue a]) ]
167 results_per_prog = map (calc_result rs f stat result_ok) (fmToList r)
169 results_per_run = transpose (map snd results_per_prog)
170 (lows,gms,highs) = unzip3 (map calc_gmsd results_per_run)
176 -> (Results -> FiniteMap String a)
180 htmlShowMultiResults (r:rs) ss f result_ok =
182 +/+ foldr1 (+/+) (map show_results_for_prog results_per_prog_mod_run)
183 +/+ foldr1 (+/+) ((cellHtml [] (bold [] (htmlStr "Average"))
184 +-+ tableRow (-1) ("", gms))
186 else [(cellHtml [] (bold [] (htmlStr "-1 s.d.")))
187 +-+ tableRow (-1) ("", lows),
188 (cellHtml [] (bold [] (htmlStr "+1 s.d.")))
189 +-+ tableRow (-1) ("", highs)])
191 base_results = fmToList r :: [(String,Results)]
193 -- results_per_prog_mod_run :: [(String,[(String,[BoxValue a])])]
194 results_per_prog_mod_run = map get_results_for_prog base_results
196 -- get_results_for_prog :: (String,Results) -> (String,[BoxValue a])
197 get_results_for_prog (prog,r) = (prog, map get_results_for_mod (fmToList (f r)))
199 where fms = map get_run_results rs
201 get_run_results fm = case lookupFM fm prog of
205 get_results_for_mod (id,attr) = calc_result fms Just (const Success)
208 show_results_for_prog (prog,mrs) =
209 cellHtml [valign "top"] (bold [] (htmlStr prog))
210 +-+ (if null mrs then
211 cellHtml [] (htmlStr "(no modules compiled)")
213 foldr1 (+/+) (map (tableRow 0) mrs))
215 results_per_run = transpose [xs | (_,mods) <- results_per_prog_mod_run,
217 (lows,gms,highs) = unzip3 (map calc_gmsd results_per_run)
219 tableRow :: Result a => Int -> (String, [BoxValue a]) -> HtmlTable
220 tableRow row_no (prog, results)
221 = cellHtml [bgcolor left_column_color] (htmlStr prog)
222 +-+ foldr1 (+-+) (map (cellHtml [align "right", clr]
223 . htmlStr . show_box) results)
224 where clr | row_no < 0 = bgcolor average_row_color
225 | even row_no = bgcolor even_row_color
226 | otherwise = bgcolor odd_row_color
228 left_column_color = "#d0d0ff" -- light blue
229 odd_row_color = "#d0d0ff" -- light blue
230 even_row_color = "#f0f0ff" -- v. light blue
231 average_row_color = "#ffd0d0" -- light red
234 findBest :: Result a => [BoxValue a] -> [(Bool,BoxValue a)]
235 findBest stuff@(Result base : rest)
236 = map (\a -> (a==base, a))
238 best = snd (minimumBy (\a b -> fst a < fst b) no_pcnt_stuff
240 no_pcnt_stuff = map unPcnt stuff
242 unPcnt (r@(Percentage f) : rest) = (base * f/100, r) : unPcnt rest
243 unPcnt (r@(Result a) : rest) = (a, r) : unPcnt rest
244 unPcnt (_ : rest) = unPcnt rest
248 = foldr1 (+-+) (map (\s -> cellHtml [align "right", width "100"]
249 (bold [] (htmlStr s))) ss)
251 mkTable :: HtmlTable -> Html
252 mkTable = renderTable [cellspacing 0, cellpadding 0, border 0]
255 = cellHtml [align "left", width "100"] (bold [] (htmlStr "Program"))
259 = cellHtml [align "left", width "100"] (bold [] (htmlStr "Program"))
260 +-+ cellHtml [align "left", width "100"] (bold [] (htmlStr "Module"))
263 -- Calculate a color ranging from bright blue for -100% to bright red for +100%.
265 calcColor :: Int -> String
266 calcColor p | p >= 0 = "#" ++ (showHex red 2 "0000")
267 | otherwise = "#0000" ++ (showHex blue 2 "")
268 where red = p * 255 `div` 100
269 blue = (-p) * 255 `div` 100
271 showHex 0 f s = if f > 0 then take f (repeat '0') ++ s else s
272 showHex i f s = showHex (i `div` 16) (f-1) (hexDig (i `mod` 16) : s)
274 hexDig i | i > 10 = chr (i-10 + ord 'a')
275 | otherwise = chr (i + ord '0')
277 -----------------------------------------------------------------------------
278 -- ASCII page generation
280 asciiPage results args =
281 ( interleave "\n\n" (map (asciiGenProgTable results args) per_prog_result_tab)
283 . interleave "\n\n" (map (asciiGenModTable results args) per_module_result_tab)
286 asciiGenProgTable results args (SpecP title anc get_result get_status result_ok)
289 . ascii_show_results results args get_result get_status result_ok
291 asciiGenModTable results args (SpecM title anc get_result result_ok)
294 . ascii_show_multi_results results args get_result result_ok
297 = str "\n-------------------------------------------------------------------------------\n"
298 . str (rjustify 15 "Program")
300 . foldr (.) id (map (str . rjustify fIELD_WIDTH) ss)
301 . str "\n-------------------------------------------------------------------------------\n"
307 -> (Results -> Maybe a)
308 -> (Results -> Status)
312 ascii_show_results (r:rs) ss f stat result_ok
314 . interleave "\n" (map show_per_prog_results results_per_prog)
316 . show_per_prog_results ("Average",gms)
319 . show_per_prog_results ("-1 s.d.",lows)
321 . show_per_prog_results ("+1 s.d.",highs)
323 -- results_per_prog :: [ (String,[BoxValue a]) ]
324 results_per_prog = map (calc_result rs f stat result_ok) (fmToList r)
326 results_per_run = transpose (map snd results_per_prog)
327 (lows,gms,highs) = unzip3 (map calc_gmsd results_per_run)
329 ascii_show_multi_results
333 -> (Results -> FiniteMap String a)
337 ascii_show_multi_results (r:rs) ss f result_ok
339 . interleave "\n" (map show_results_for_prog results_per_prog_mod_run)
342 . show_per_prog_results ("Average",gms)
345 . show_per_prog_results ("-1 s.d.",lows)
347 . show_per_prog_results ("+1 s.d.",highs)
349 base_results = fmToList r :: [(String,Results)]
351 -- results_per_prog_mod_run :: [(String,[(String,[BoxValue a])])]
352 results_per_prog_mod_run = map get_results_for_prog base_results
354 -- get_results_for_prog :: (String,Results) -> (String,[BoxValue a])
355 get_results_for_prog (prog,r) = (prog, map get_results_for_mod (fmToList (f r)))
357 where fms = map get_run_results rs
359 get_run_results fm = case lookupFM fm prog of
363 get_results_for_mod (id,attr) = calc_result fms Just (const Success)
366 show_results_for_prog (prog,mrs) =
367 str ("\n"++prog++"\n")
369 str "(no modules compiled)\n"
371 interleave "\n" (map show_per_prog_results mrs))
373 results_per_run = transpose [xs | (_,mods) <- results_per_prog_mod_run,
375 (lows,gms,highs) = unzip3 (map calc_gmsd results_per_run)
377 show_per_prog_results :: Result a => (String, [BoxValue a]) -> ShowS
378 show_per_prog_results (prog,results)
379 = str (rjustify 15 prog)
381 . foldr (.) id (map (str . rjustify fIELD_WIDTH . show_box) results)
383 -----------------------------------------------------------------------------
386 class Num a => Result a where
387 result_to_string :: a -> String
388 convert_to_percentage :: a -> a -> Float
390 -- We assume an Int is a size, and print it in kilobytes.
392 instance Result Int where
393 convert_to_percentage 0 size = 100
394 convert_to_percentage base size = (fromInt size / fromInt base) * 100
396 result_to_string n = show (n `div` 1024) ++ "k"
398 instance Result Integer where
399 convert_to_percentage 0 size = 100
400 convert_to_percentage base size = (fromInteger size / fromInteger base) * 100
402 result_to_string n = show (n `quot` 1024) ++ "k"
404 instance Result Float where
405 convert_to_percentage 0.0 size = 100.0
406 convert_to_percentage base size = size / base * 100
408 result_to_string = showFloat' Nothing (Just 2)
410 data BoxValue a = RunFailed Status | Percentage Float | Result a
412 -- calc_result is a nice exercise in higher-order programming...
415 => [FiniteMap String b] -- accumulated results
416 -> (b -> Maybe a) -- get a result from the b
417 -> (b -> Status) -- get a status from the b
418 -> (a -> Bool) -- is this result ok?
419 -> (String,b) -- the baseline result
420 -> (String,[BoxValue a])
422 calc_result rts get_maybe_a get_stat result_ok (prog,base_r) =
423 (prog, (just_result baseline base_stat :
426 rts' = map (\rt -> get_stuff (lookupFM rt prog)) rts
428 get_stuff Nothing = (Nothing, NotDone)
429 get_stuff (Just r) = (get_maybe_a r, get_stat r)
433 Just base | result_ok base
434 -> map (\(r,s) -> percentage r s base) rts'
436 -> map (\(r,s) -> just_result r s) rts'
439 baseline = get_maybe_a base_r
440 base_stat = get_stat base_r
442 just_result Nothing s = RunFailed s
443 just_result (Just a) s = Result a
445 percentage Nothing s base = RunFailed s
446 percentage (Just a) s base = Percentage
447 (convert_to_percentage base a)
448 show_box (RunFailed s) = show_stat s
449 show_box (Percentage p) = show_pcntage p
450 show_box (Result a) = result_to_string a
452 -----------------------------------------------------------------------------
453 -- Calculating geometric means and standard deviations
456 This is done using the log method, to avoid needing really large
457 intermediate results. The formula for a geometric mean is
459 (a1 * .... * an) ^ 1/n
461 which is equivalent to
463 e ^ ( (log a1 + ... + log an) / n )
465 where log is the natural logarithm function.
467 Similarly, to compute the geometric standard deviation we compute the
468 deviation of each log, take the root-mean-square, and take the
471 e ^ sqrt( ( sqr(log a1 - lbar) + ... + sqr(log an - lbar) ) / n )
473 where lbar is the mean log,
475 (log a1 + ... + log an) / n
477 This is a *factor*: i.e., the 1 s.d. points are (gm/sdf,gm*sdf); do
478 not subtract 100 from gm before performing this calculation.
480 We therefore return a (low, mean, high) triple.
484 calc_gmsd :: [BoxValue a] -> (BoxValue Float, BoxValue Float, BoxValue Float)
486 | null percentages = (RunFailed NotDone, RunFailed NotDone, RunFailed NotDone)
487 | otherwise = let sqr x = x * x
488 len = fromInt (length percentages)
489 logs = map log percentages
490 lbar = sum logs / len
491 devs = map (sqr . (lbar-)) logs
492 dbar = sum devs / len
494 sdf = exp (sqrt dbar)
496 (Percentage (gm/sdf),
500 percentages = [ if f < 5 then 5 else f | Percentage f <- xs ]
501 -- can't do log(0.0), so exclude zeros
502 -- small values have inordinate effects so cap at -95%.
504 -----------------------------------------------------------------------------
505 -- Generic stuff for results generation
507 show_pcntage n = show_float_signed (n-100) ++ "%"
509 show_float_signed = showFloat False False True False False Nothing (Just 2)
511 show_stat Success = "(no result)"
512 show_stat WrongStdout = "(stdout)"
513 show_stat WrongStderr = "(stderr)"
514 show_stat (Exit x) = "exit(" ++ show x ++")"
515 show_stat OutOfHeap = "(heap)"
516 show_stat OutOfStack = "(stack)"
517 show_stat NotDone = "-----"
521 interleave s = foldr1 (\a b -> a . str s . b)
523 fIELD_WIDTH = 16 :: Int
525 -----------------------------------------------------------------------------