{-# LANGUAGE OverloadedStrings #-} {- | Module : GHC.Vis.View.Graph.Parser Copyright : (c) Dennis Felsing License : 3-Clause BSD-style Maintainer : dennis@felsin9.de -} module GHC.Vis.View.Graph.Parser ( xDotParse ) where import Data.List import Control.Monad import qualified Data.Text.Lazy as B import Data.Graph.Inductive hiding (nodes, edges) import Data.GraphViz hiding (Ellipse, Polygon, parse) import Data.GraphViz.Attributes.Complete import Data.GraphViz.Commands.IO import GHC.HeapView hiding (name) import GHC.Vis.Internal import GHC.Vis.Types import Graphics.XDot.Types hiding (name, h, Style, Color) import Graphics.XDot.Parser fontName :: B.Text --fontName = "Times Roman" --fontName = "Helvetica" fontName = "Sans" graphFontSize :: Double graphFontSize = 24 nodeFontSize :: Double nodeFontSize = 24 edgeFontSize :: Double edgeFontSize = 24 foldlMaybe :: (a -> b -> Maybe a) -> a -> [b] -> Maybe a foldlMaybe f a bs = foldr (\b g x -> f x b >>= g) Just bs a -- | Take the objects to be visualized and run them through @dot@ and extract -- the drawing operations that have to be exectued to show the graph of the -- heap map. xDotParse :: [(Box, String)] -> IO ([(Object Node, Operation)], [Box], Rectangle) xDotParse as = do hm <- walkHeap as let nodes = zip [0..] $ map (\(_,(_,c)) -> c) rhm edges = do mbe <- foldM mbEdges [] nodes return $ foldlMaybe toLEdge [] mbe -- Reversing it fixes the ordering of nodes in the graph. Should run -- through allPtrs and sort by order inside of all allPtrs lists. -- -- When building the graph directly out of [Box] instead of going -- through the HeapMap, then the order of nodes might not be right for -- non-trivial graphs. -- -- In some cases it's impossible to get the order right. Maybe there is -- a way in graphviz to specify outgoing edge orientation after all? rhm = reverse hm toLEdge xs (0, Just t) = if length rhm <= t then Nothing -- This might be able to happen, let's make sure it doesn't else case rhm !! t of (_,(Just name, _)) -> Just $ (0,t,name):xs (_,(Nothing, _)) -> Just $ (0,t,""):xs toLEdge xs (f, Just t) = Just $ (f,t,""):xs toLEdge xs _ = Just xs mbEdges xs (p,BCOClosure _ _ _ bPtr _ _ _) = do children <- bcoChildren [bPtr] return $ map (\b -> (p, Just b)) children ++ xs -- Using allPtrs and then filtering the closures not available in the -- heap map out emulates pointersToFollow without being in IO mbEdges xs (p,c) = return $ map (\b -> (p, boxPos b)) (allPtrs c) ++ xs boxPos :: Box -> Maybe Int boxPos b = elemIndex b $ map fst rhm bcoChildren :: [Box] -> IO [Int] bcoChildren [] = return [] bcoChildren (b:bs) = case boxPos b of Nothing -> do c <- getBoxedClosureData b ptf <- pointersToFollow2 c bcoChildren (ptf ++ bs) Just pos -> do children <- bcoChildren bs return $ pos : children es' <- edges case es' of Nothing -> xDotParse as Just es -> do -- Convert a heap map, our internal data structure, to a graph that can be -- converted to a dot graph. let buildGraph :: Gr Closure String buildGraph = insEdges es $ insNodes nodes empty xDot <- graphvizWithHandle Dot (defaultVis $ toViewableGraph buildGraph) XDot hGetDot return (getOperations xDot, getBoxes hm, getSize xDot) getBoxes :: HeapMap -> [Box] getBoxes hm = map (\(b,(_,_)) -> b) $ reverse hm -- Probably have to do some kind of fold over the graph to remove for example -- unwanted pointers toViewableGraph :: Gr Closure String -> Gr String String toViewableGraph cg = emap id $ nmap showClosure cg defaultVis :: (Graph gr) => gr String String -> DotGraph Node defaultVis = graphToDot nonClusteredParams -- Somehow (X11Color Transparency) is white, use (RGBA 0 0 0 0) instead -- Ordering OutEdges is not strong enough to force edge ordering, might not look good anyway { globalAttributes = [GraphAttrs [BgColor [RGBA 0 0 0 0], FontName fontName, FontSize graphFontSize]] , fmtNode = \ (_,l) -> [toLabel l, FontName fontName, FontSize nodeFontSize] --, fmtNode = \ (_,l) -> [toLabel l, FontName fontName, FontSize nodeFontSize, Style [SItem Filled []], FillColor [RGBA 255 255 255 255], Color [RGBA 0 0 0 255]] --, fmtNode = \ (_,l) -> [toLabel l, FontName fontName, FontSize nodeFontSize, Shape PlainText] , fmtEdge = \ (_,_,l) -> [toLabel l, FontName fontName, FontSize edgeFontSize] }