-- Copyright (c) 2004 Duncan Coutts -- This library is liscenced under the GNU General Public License version 2 -- or (at your option) any later version. -- This is a not-terribly-clever parser for ghc's time profile log files. module ParseProfile ( Profile(..), ProfileNode(..), parseProfileFile, pruneOnThreshold ) where import Data.Char import Data.Maybe (catMaybes) data Profile = Profile { title :: String, command :: String, totalTime :: Float, totalAlloc :: Integer, --can be several GB breakdown :: ProfileNode } data ProfileNode = ProfileNode { costCentre :: String, moduleName :: String, entries :: !Int, individualTime :: !Int, --scaled by 10 individualAlloc :: !Int, --scaled by 10 inheritedTime :: !Int, --scaled by 10 inheritedAlloc :: !Int, --scaled by 10 children :: [ProfileNode] } pruneOnThreshold :: Int -> ProfileNode -> Maybe ProfileNode pruneOnThreshold threshold node | inheritedTime node >= threshold || inheritedAlloc node >= threshold = let children' = catMaybes $ map (pruneOnThreshold threshold) (children node) in Just $ node { children = children' } | otherwise = Nothing parseProfileFile :: String -> IO Profile parseProfileFile filename = do content <- readFile filename let (titleLine:_:commandLine:_:timeLine:allocLine:theRest) = lines content profileDetail = dropWhile (\line -> take 4 line /= "MAIN") theRest return $ Profile { title = dropWhile isSpace titleLine, command = dropWhile isSpace commandLine, totalTime = read $ words timeLine !! 3, totalAlloc = read $ filter (/=',') $ words allocLine !! 3, breakdown = parseProfile profileDetail } -- intermediate form data ProfileEntry = ProfileEntry { depth :: !Int, ecostCentre :: String, emoduleName :: String, eentries :: !Int, eindividualTime :: !Int, --scaled by 10 eindividualAlloc :: !Int, --scaled by 10 einheritedTime :: !Int, --scaled by 10 einheritedAlloc :: !Int --scaled by 10 } parseProfile :: [String] -> ProfileNode parseProfile file = case (profileEntriesToProfile [] 0 . map parseProfileEntry) file of ([profile],[]) -> profile _ -> error "multiple top level entries" parseProfileEntry :: String -> ProfileEntry parseProfileEntry line = let depth = length (takeWhile (==' ') line) in case words line of [costCentre, moduleName, _, entries, individualTime, individualAlloc, inheritedTime, inheritedAlloc] -> ProfileEntry { depth = depth, ecostCentre = costCentre, emoduleName = moduleName, eentries = read entries, eindividualTime = floor $ (read individualTime) * 10, eindividualAlloc = floor $ (read individualAlloc) * 10, einheritedTime = floor $ (read inheritedTime) * 10, einheritedAlloc = floor $ (read inheritedAlloc) * 10 } _ -> error $ "bad profile line:\n\t" ++ line profileEntriesToProfile :: [ProfileNode] -> Int -> [ProfileEntry] -> ([ProfileNode], [ProfileEntry]) profileEntriesToProfile acum curDepth [] = (acum, []) profileEntriesToProfile acum curDepth (entry:entries) | depth entry == curDepth = let (children, remaining) = profileEntriesToProfile [] (depth entry + 1) entries curNode = ProfileNode { costCentre = ecostCentre entry, moduleName = emoduleName entry, entries = eentries entry, individualTime = eindividualTime entry, individualAlloc = eindividualAlloc entry, inheritedTime = einheritedTime entry, inheritedAlloc = einheritedAlloc entry, children = children } in profileEntriesToProfile (curNode:acum) (depth entry) remaining | depth entry < curDepth = (acum, entry:entries) --we're done for this level | otherwise = error "bad indentation in file"