{-# LANGUAGE OverloadedStrings #-} {- | Module : GHC.Vis.Graph Copyright : (c) Dennis Felsing License : 3-Clause BSD-style Maintainer : dennis@felsin9.de -} module GHC.Vis.Graph ( xDotParse ) where import System.IO.Unsafe import Data.Text.IO import qualified Data.Text.Lazy as B import Data.Graph.Inductive hiding (nodes, edges) import Data.GraphViz hiding (Ellipse, Polygon, parse) import qualified Data.GraphViz.Types.Generalised as G import Data.GraphViz.Attributes.Complete import GHC.HeapView hiding (name) import GHC.Vis.Internal import GHC.Vis.Types import Graphics.XDot.Types hiding (name, h) import Graphics.XDot.Parser fontName :: B.Text --fontName = "Times Roman" fontName = "DejaVu Sans" graphFontSize :: Double graphFontSize = 24 nodeFontSize :: Double nodeFontSize = 24 edgeFontSize :: Double edgeFontSize = 24 -- | 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 ([(Maybe Node, Operation)], [Box], Rectangle) xDotParse as = do (dotGraph, boxes) <- dg as return (getOperations dotGraph, boxes, getSize dotGraph) dg :: [(Box, String)] -> IO (G.DotGraph Node, [Box]) dg as = do hm <- walkHeap as --hm <- walkHeapDepth as xDotText <- graphvizWithHandle Dot (defaultVis $ toViewableGraph $ buildGraph hm) XDot hGetContents return (parseDotGraph $ B.fromChunks [xDotText], getBoxes hm) -- | Convert a heap map, our internal data structure, to a graph that can be -- converted to a dot graph. buildGraph :: HeapMap -> Gr Closure String buildGraph hm = insEdges edges $ insNodes nodes empty where nodes = zip [0..] $ map (\(_,(_,c)) -> c) rhm edges = foldr toLEdge [] $ foldr mbEdges [] nodes -- 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 (0, Just t) xs = case rhm !! t of (_,(Just name, _)) -> (0,t,name):xs (_,(Nothing, _)) -> (0,t,""):xs toLEdge (f, Just t) xs = (f,t,""):xs toLEdge _ xs = xs mbEdges (p,BCOClosure _ _ _ bPtr _ _ _) xs = map (\b -> (p, Just b)) (bcoChildren [bPtr] hm) ++ xs -- Using allPtrs and then filtering the closures not available in the -- heap map out emulates pointersToFollow without being in IO mbEdges (p,c) xs = map (\b -> (p, boxPos b)) (allPtrs c) ++ xs boxPos :: Box -> Maybe Int boxPos b = lookup b $ zip (map fst rhm) [0..] bcoChildren :: [Box] -> HeapMap -> [Int] bcoChildren [] _ = [] bcoChildren (b:bs) h = case boxPos b of Nothing -> let ptf = unsafePerformIO $ getBoxedClosureData b >>= pointersToFollow2 in bcoChildren (ptf ++ bs) h -- Could go into infinite loop Just pos -> pos : bcoChildren bs h 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 { globalAttributes = [GraphAttrs [BgColor [RGBA 0 0 0 0], FontName fontName, FontSize graphFontSize]] , fmtNode = \ (_,l) -> [toLabel l, FontName fontName, FontSize nodeFontSize] , fmtEdge = \ (_,_,l) -> [toLabel l, FontName fontName, FontSize edgeFontSize] }