llvm-tools-0.2.0.0: src/LLVM/VisualizeGraph.hs
module LLVM.VisualizeGraph (
OutputType(..),
visualizeGraph
) where
import GHC.Conc ( getNumCapabilities )
import Control.Arrow
import Control.Concurrent.ParallelIO.Local
import Control.Monad ( when )
import qualified Data.ByteString as BS
import Data.GraphViz
import Data.Maybe ( isNothing )
import System.Directory
import System.FilePath
import System.FilePath.Glob
import Paths_llvm_tools
import LLVM.Analysis
import LLVM.Analysis.Util.Testing ( buildModule )
import LLVM.Parse ( defaultParserOptions, parseLLVMFile )
import LLVM.HtmlWrapper
import LLVM.SvgInspection
data OutputType = CanvasOutput GraphvizCanvas
| FileOutput GraphvizOutput
| HtmlOutput
deriving (Show)
-- | Visualize a graph-based analysis with graphviz. It handles many
-- common options including both file and canvas output.
visualizeGraph :: (ToGraphviz a)
=> FilePath -- ^ Input file name
-> Maybe FilePath -- ^ Output file name
-> OutputType -- ^ Type of output requested
-> [String] -- ^ Module optimization flags
-> (Module -> [(String, a)]) -- ^ A function to turn a Module into some graphs
-> IO ()
visualizeGraph inFile outFile fmt optOptions fromModule = do
let p = parseLLVMFile defaultParserOptions
m <- buildModule [] optOptions p inFile
let gs = fromModule m
case fmt of
HtmlOutput -> do
when (isNothing outFile) $ ioError $ userError "Output directory not specified"
-- Make a directory for all of the output and render each graph
-- with graphviz to svg format. For each svg, create an html
-- wrapper page (with an index page). The html page should be simple
-- and just embed the SVG and load svgpan (and maybe jquery)
let Just outFile' = outFile
gdir = outFile' </> "graphs"
createDirectoryIfMissing True gdir
let jsAndCss = [ "OpenLayers.js"
, "jquery-1.7.1.js"
, "showGraph.js"
, "graph.css"
]
mapM_ (installStaticFile gdir) jsAndCss
installStaticSubdir "img" gdir
caps <- getNumCapabilities
let actions = map (makeFunctionPage toGraphviz gdir) gs
withPool caps $ \capPool -> parallel_ capPool actions
writeHtmlIndex outFile' (map fst gs)
-- If we are showing canvases, ignore function names
CanvasOutput o -> mapM_ (\(_,g) -> runGraphvizCanvas' (toGraphviz g) o) gs
FileOutput o -> do
when (isNothing outFile) $ ioError $ userError "Output file not specified"
let Just outFile' = outFile
case gs of
[(_, g)] -> runGraphviz (toGraphviz g) o outFile' >> return ()
_ -> do
-- If we have more than one function, put all of them in
-- the given directory
createDirectoryIfMissing True outFile'
mapM_ (writeDotGraph toGraphviz outFile' o) gs
installStaticFile :: FilePath -> FilePath -> IO ()
installStaticFile dir name = do
file <- getDataFileName ("share" </> name)
copyFile file (dir </> name)
-- | Install all of the files in the given subdir of the datadir to a
-- destdir. The subdir is created (this is basically cp -R).
installStaticSubdir :: FilePath -> FilePath -> IO ()
installStaticSubdir sdir destdir = do
dd <- getDataDir
let patt = dd </> "share" </> sdir </> "*"
files <- namesMatching patt
let toDest f = destdir </> sdir </> takeFileName f
let namesAndDests = map (id &&& toDest) files
createDirectoryIfMissing True (destdir </> sdir)
mapM_ (uncurry copyFile) namesAndDests
makeFunctionPage :: (PrintDotRepr dg n)
=> (a -> dg n)
-> FilePath
-> (FilePath, a)
-> IO ()
makeFunctionPage toGraph gdir (fname, g) = do
let svgname = gdir </> gfilename
dg = toGraph g
-- Use the more general graphvizWithHandle so that we can read the
-- generated image before writing it to disk. This lets us extract
-- its dimensions. The other alternative is to just use the default
-- graphviz to create a file and then read the file - this led to
-- races when writing with multiple threads. This approach is
-- safer.
dims <- graphvizWithHandle (commandFor dg) dg Svg $ \h -> do
svgContent <- BS.hGetContents h
BS.writeFile svgname svgContent
return (getSvgSize svgContent)
let Just (w, h) = dims
writeHtmlWrapper gdir hfilename gfilename fname w h
where
gfilename = fname <.> "svg"
hfilename = fname <.> "html"
writeDotGraph :: (PrintDotRepr dg n)
=> (a -> dg n)
-> FilePath
-> GraphvizOutput
-> (FilePath, a)
-> IO ()
writeDotGraph toGraph dirname o (funcName, g) =
runGraphviz (toGraph g) o filename >> return ()
where
filename = dirname </> funcName <.> toExt o
toExt :: GraphvizOutput -> String
toExt o =
case o of
Canon -> "dot"
XDot -> "dot"
Eps -> "eps"
Fig -> "fig"
Jpeg -> "jpg"
Pdf -> "pdf"
Png -> "png"
Ps -> "ps"
Ps2 -> "ps"
Svg -> "svg"
_ -> error $ "Unsupported format: " ++ show o