packages feed

ghc-debug-client (empty) → 0.1.0.0

raw patch · 25 files changed

+2705/−0 lines, 25 filesdep +asyncdep +basedep +binarysetup-changed

Dependencies added: async, base, binary, bitwise, containers, directory, dom-lt, eventlog2html, filepath, ghc-debug-common, ghc-debug-convention, ghc-prim, hashable, haxl, language-dot, monoidal-containers, mtl, network, process, psqueues, stm, text, unordered-containers

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for ghc-debug-client++## 0.1.0.0 -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2019, Ben Gamari++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Ben Gamari nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ ghc-debug-client.cabal view
@@ -0,0 +1,68 @@+cabal-version:       3.0+name:                ghc-debug-client+version:             0.1.0.0+synopsis:            Useful functions for writing heap analysis tools which use+                     ghc-debug.+description:         Useful functions for writing heap analysis tools which use+                     ghc-debug.+homepage:            https://gitlab.haskell.org/ghc/ghc-debug+license:             BSD-3-Clause+license-file:        LICENSE+author:              Ben Gamari, Matthew Pickering, David Eichmann+maintainer:          matthewtpickering@gmail.com+copyright:           (c) 2019-2021 Ben Gamari, Matthew Pickering+category:            Development+build-type:          Simple+extra-source-files:  CHANGELOG.md++library+  exposed-modules:     GHC.Debug.Client,+                       GHC.Debug.Retainers,+                       GHC.Debug.GML,+                       GHC.Debug.Fragmentation,+                       GHC.Debug.ObjectEquiv,+                       GHC.Debug.Client.Search,+                       GHC.Debug.Profile,+                       GHC.Debug.Profile.Types,+                       GHC.Debug.Trace,+                       GHC.Debug.ParTrace,+                       GHC.Debug.Count,+                       GHC.Debug.TypePointsFrom,+                       GHC.Debug.Snapshot,+                       GHC.Debug.Dominators,+                       GHC.Debug.Client.Query,+                       GHC.Debug.Client.Monad,+                       GHC.Debug.Client.BlockCache,+                       GHC.Debug.Client.RequestCache,+                       GHC.Debug.Client.Monad.Class,+                       GHC.Debug.Client.Monad.Haxl,+                       GHC.Debug.Client.Monad.Simple++  build-depends:       base >=4.16 && < 4.17,+                       network >= 2.6 ,+                       containers ^>= 0.6,+                       unordered-containers ^>= 0.2.13,+                       ghc-debug-common ^>= 0.1,+                       ghc-debug-convention ^>= 0.1,+                       text ^>= 1.2.4,+                       process ^>= 1.6,+                       filepath ^>= 1.4,+                       directory ^>= 1.3,+                       bitwise ^>= 1.0,+                       haxl ^>= 2.3,+                       hashable ^>= 1.3,+                       mtl ^>= 2.2,+                       eventlog2html >= 0.8.3,+                       binary ^>= 0.8,+                       psqueues ^>= 0.2,+                       dom-lt ^>= 0.2,+                       async ^>= 2.2,+                       monoidal-containers >= 0.6,+                       language-dot ^>= 0.1,+                       ghc-prim ^>= 0.8,+                       stm ^>= 2.5++  hs-source-dirs:      src+  default-language:    Haskell2010+  default-extensions:  ApplicativeDo+  ghc-options:         -Wall
+ src/GHC/Debug/Client.hs view
@@ -0,0 +1,124 @@+{- | The main API for creating debuggers. For example, this API can be used+to connect to an instrumented process, query the GC roots and then decode+the first root up to depth 10 and displayed to the user.++@+main = withDebuggeeConnect "\/tmp\/ghc-debug" p1++p1 :: Debuggee -> IO ()+p1 e = do+  pause e+  g <- run e $ do+        precacheBlocks+        (r:_) <- gcRoots+        buildHeapGraph (Just 10) r+  putStrLn (ppHeapGraph (const "") h)+@++-}+module GHC.Debug.Client+  ( -- * Running/Connecting to a debuggee+    Debuggee+  , DebugM+  , debuggeeRun+  , debuggeeConnect+  , debuggeeClose+  , withDebuggeeRun+  , withDebuggeeConnect+  , socketDirectory+  , snapshotRun++    -- * Running DebugM+  , run+  , runTrace+  , runAnalysis++    -- * Pause/Resume+  , pause+  , fork+  , pauseThen+  , resume+  , pausePoll+  , withPause++  -- * Basic Requests+  , version+  , gcRoots+  , allBlocks+  , getSourceInfo+  , savedObjects+  , precacheBlocks+  , dereferenceClosure+  , dereferenceClosures+  , dereferenceStack+  , dereferencePapPayload+  , dereferenceConDesc+  , dereferenceInfoTable++  , Quadtraversable(..)++  -- * Building a Heap Graph+  , buildHeapGraph+  , multiBuildHeapGraph+  , HG.HeapGraph(..)+  , HG.HeapGraphEntry(..)++  -- * Printing a heap graph+  , HG.ppHeapGraph++  -- * Tracing+  , traceWrite+  , traceMsg++  -- * Caching+  , saveCache+  , loadCache++  -- * Types+  , module GHC.Debug.Types.Closures+  , SourceInformation(..)+  , RawBlock(..)+  , BlockPtr+  , StackPtr+  , ClosurePtr+  , InfoTablePtr+  , HG.StackHI+  , HG.PapHI+  , HG.HeapGraphIndex+  ) where++import           GHC.Debug.Types+import           GHC.Debug.Types.Closures+import           GHC.Debug.Convention (socketDirectory)+import GHC.Debug.Client.Monad+import           GHC.Debug.Client.Query+import qualified GHC.Debug.Types.Graph as HG+import Data.List.NonEmpty (NonEmpty)++derefFuncM :: HG.DerefFunction DebugM Size+derefFuncM c = do+  c' <- dereferenceClosure c+  quadtraverse dereferencePapPayload dereferenceConDesc dereferenceStack pure c'++-- | Build a heap graph starting from the given root. The first argument+-- controls how many levels to recurse. You nearly always want to set this+-- to a small number ~ 10, as otherwise you can easily run out of memory.+buildHeapGraph :: Maybe Int -> ClosurePtr -> DebugM (HG.HeapGraph Size)+buildHeapGraph = HG.buildHeapGraph derefFuncM++-- | Build a heap graph starting from multiple roots. The first argument+-- controls how many levels to recurse. You nearly always want to set this+-- value to a small number ~ 10 as otherwise you can easily run out of+-- memory.+multiBuildHeapGraph :: Maybe Int -> NonEmpty ClosurePtr -> DebugM (HG.HeapGraph Size)+multiBuildHeapGraph = HG.multiBuildHeapGraph derefFuncM++-- | Perform the given analysis whilst the debuggee is paused, then resume+-- and apply the continuation to the result.+runAnalysis :: DebugM a -> (a -> IO r) -> Debuggee -> IO r+runAnalysis a k e = do+  pause e+  r <- run e a+  resume e+  k r+
+ src/GHC/Debug/Client/BlockCache.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE BinaryLiterals #-}+{-# LANGUAGE RankNTypes #-}+-- The BlockCache stores the currently fetched blocks+-- and is consulted first to avoid requesting too much+-- from the debuggee. The BlockCache can either be populated+-- via a call to RequestBlocks or on demand on a cache miss.++module GHC.Debug.Client.BlockCache(BlockCache, BlockCacheRequest(..)+                                  , handleBlockReq, emptyBlockCache, bcSize, addBlocks) where++import GHC.Debug.Types.Ptr+import GHC.Debug.Types+import qualified Data.HashMap.Strict as HM+import GHC.Word+import Data.Hashable+import Data.IORef+import Data.Bits+import Data.List (sort)+import Data.Binary++newtype BlockCache = BlockCache (HM.HashMap Word64 RawBlock)++instance Binary BlockCache where+  get = BlockCache . HM.fromList <$> get+  put (BlockCache hm) = put (HM.toList hm)++emptyBlockCache :: BlockCache+emptyBlockCache = BlockCache HM.empty++addBlock :: RawBlock -> BlockCache -> BlockCache+addBlock rb@(RawBlock (BlockPtr bp) _ _) (BlockCache bc) =+  BlockCache (HM.insert bp rb bc)+++addBlocks :: [RawBlock] -> BlockCache -> BlockCache+addBlocks bc bs = Prelude.foldr addBlock bs bc++lookupClosure :: ClosurePtr -> BlockCache -> Maybe RawBlock+lookupClosure (ClosurePtr cp) (BlockCache b) =+  HM.lookup (cp .&. complement blockMask) b++bcSize :: BlockCache -> Int+bcSize (BlockCache b) = HM.size b++_bcKeys :: BlockCache -> [ClosurePtr]+_bcKeys (BlockCache b) = sort $ map mkClosurePtr (HM.keys b)++data BlockCacheRequest a where+  LookupClosure :: ClosurePtr -> BlockCacheRequest RawClosure+  PopulateBlockCache :: BlockCacheRequest [RawBlock]++deriving instance Show (BlockCacheRequest a)+deriving instance Eq (BlockCacheRequest a)++instance Hashable (BlockCacheRequest a) where+  hashWithSalt s (LookupClosure cpt) = s `hashWithSalt` (1 :: Int) `hashWithSalt` cpt+  hashWithSalt s PopulateBlockCache  = s `hashWithSalt` (2 :: Int)++handleBlockReq :: (forall a . Request a -> IO a) -> IORef BlockCache -> BlockCacheRequest resp -> IO resp+handleBlockReq do_req ref (LookupClosure cp) = do+  bc <- readIORef ref+  let mrb = lookupClosure cp bc+  rb <- case mrb of+               Nothing -> do+                  rb <- do_req (RequestBlock cp)+                  atomicModifyIORef' ref (\bc' -> (addBlock rb bc', ()))+                  return rb+               Just rb -> do+                 return rb+  return (extractFromBlock cp rb)+handleBlockReq do_req ref PopulateBlockCache = do+  blocks <- do_req RequestAllBlocks+--  mapM_ (\rb -> print ("NEW", rawBlockAddr rb)) blocks+  print ("CACHING", length blocks)+  atomicModifyIORef' ref ((,()) . addBlocks blocks)+  return blocks+++
+ src/GHC/Debug/Client/Monad.hs view
@@ -0,0 +1,127 @@+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -Wno-unused-imports #-}+module GHC.Debug.Client.Monad+  ( DebugMonad(..)+  , run+  , DebugM+  , Debuggee+  , traceWrite+  , runTrace+    -- * Running/Connecting to a debuggee+  , withDebuggeeRun+  , withDebuggeeConnect+  , debuggeeRun+  , debuggeeConnect+  , debuggeeClose+  -- * Snapshot run+  , snapshotInit+  , snapshotRun+    -- * Logging+  , outputRequestLog+  ) where++import Control.Exception (finally)+import Network.Socket+import System.Process+import System.Environment+import GHC.Debug.Client.Monad.Class+import GHC.Debug.Types (Request(..))+import qualified GHC.Debug.Client.Monad.Haxl as H+import qualified GHC.Debug.Client.Monad.Simple as S+import System.IO++-- Modify this to switch between the haxl/non-haxl implementations+-- type DebugM = H.DebugM+type DebugM = S.DebugM++newtype Debuggee = Debuggee { debuggeeEnv :: DebugEnv DebugM }++runTrace :: Debuggee -> DebugM a -> IO a+runTrace (Debuggee e) act = do+  (r, ws) <- runDebugTrace e act+  mapM_ putStrLn ws+  return r++traceWrite :: DebugMonad m => Show a => a -> m ()+traceWrite = traceMsg . show++-- | Run a @DebugM a@ in the given environment.+run :: Debuggee -> DebugM a -> IO a+run (Debuggee d) = runDebug d++-- | Bracketed version of @debuggeeRun@. Runs a debuggee, connects to it, runs+-- the action, kills the process, then closes the debuggee.+withDebuggeeRun :: FilePath  -- ^ path to executable to run as the debuggee+                -> FilePath  -- ^ filename of socket (e.g. @"\/tmp\/ghc-debug"@)+                -> (Debuggee -> IO a)+                -> IO a+withDebuggeeRun exeName socketName action = do+    -- Start the process we want to debug+    cp <- debuggeeProcess exeName socketName+    withCreateProcess cp $ \_ _ _ _ -> do+    -- Now connect to the socket the debuggeeProcess just started+      withDebuggeeConnect socketName action++-- | Bracketed version of @debuggeeConnect@. Connects to a debuggee, runs the+-- action, then closes the debuggee.+withDebuggeeConnect :: FilePath  -- ^ filename of socket (e.g. @"\/tmp\/ghc-debug"@)+                    -> (Debuggee -> IO a)+                    -> IO a+withDebuggeeConnect socketName action = do+    new_env <- debuggeeConnect socketName+    action new_env+      `finally`+      debuggeeClose new_env++-- | Run a debuggee and connect to it. Use @debuggeeClose@ when you're done.+debuggeeRun :: FilePath  -- ^ path to executable to run as the debuggee+            -> FilePath  -- ^ filename of socket (e.g. @"\/tmp\/ghc-debug"@)+            -> IO Debuggee+debuggeeRun exeName socketName = do+    -- Start the process we want to debug+    _ <- createProcess =<< debuggeeProcess exeName socketName+    -- Now connect to the socket the debuggeeProcess just started+    debuggeeConnect socketName++-- | Connect to a debuggee on the given socket. Use @debuggeeClose@ when you're done.+debuggeeConnect :: FilePath  -- ^ filename of socket (e.g. @"\/tmp\/ghc-debug"@)+                -> IO Debuggee+debuggeeConnect socketName = do+    s <- socket AF_UNIX Stream defaultProtocol+    connect s (SockAddrUnix socketName)+    hdl <- socketToHandle s ReadWriteMode+    new_env <- newEnv @DebugM (SocketMode hdl)+    return (Debuggee new_env)++-- | Create a debuggee by loading a snapshot created by 'snapshot'.+snapshotInit :: FilePath -> IO Debuggee+snapshotInit fp = Debuggee <$> newEnv @DebugM (SnapshotMode fp)++-- | Start an analysis session using a snapshot. This will not connect to a+-- debuggee. The snapshot is created by 'snapshot'.+snapshotRun :: FilePath -> (Debuggee -> IO a) -> IO a+snapshotRun fp k = do+  denv <- snapshotInit fp+  k denv++-- | Close the connection to the debuggee.+debuggeeClose :: Debuggee -> IO ()+debuggeeClose d = run d $ request RequestResume++debuggeeProcess :: FilePath -> FilePath -> IO CreateProcess+debuggeeProcess exe sockName = do+  e <- getEnvironment+  return $+    (proc exe []) { env = Just (("GHC_DEBUG_SOCKET", sockName) : e) }++outputRequestLog :: Debuggee -> IO ()+outputRequestLog = printRequestLog @DebugM . debuggeeEnv+
+ src/GHC/Debug/Client/Monad/Class.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+module GHC.Debug.Client.Monad.Class where++import Data.Typeable+import GHC.Debug.Client.BlockCache+import GHC.Debug.Types+import System.IO++class (MonadFail m, Monad m) => DebugMonad m where+  type DebugEnv m+  request :: (Show resp, Typeable resp) => Request resp -> m resp+  requestBlock :: (Show resp, Typeable resp) => BlockCacheRequest resp -> m resp+  traceMsg :: String -> m ()+  printRequestLog :: DebugEnv m -> IO ()+  runDebug :: DebugEnv m -> m a -> IO a+  runDebugTrace :: DebugEnv m -> m a -> IO (a, [String])+  newEnv :: Mode -> IO (DebugEnv m)++  saveCache :: FilePath -> m ()+  loadCache :: FilePath -> m ()++  unsafeLiftIO :: IO a -> m a+++data Mode = SnapshotMode FilePath | SocketMode Handle
+ src/GHC/Debug/Client/Monad/Haxl.hs view
@@ -0,0 +1,183 @@+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE GADTs #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module GHC.Debug.Client.Monad.Haxl+  ( Debuggee(..)+  , Request(..)+  , DebugM+  , Env(..)+  ) where++import GHC.Debug.Types+import qualified Data.HashMap.Strict as HM+import System.IO+import Control.Concurrent++import GHC.Debug.Client.BlockCache+import GHC.Debug.Client.Monad.Class++import Haxl.Core hiding (Request, env)+import Data.Typeable++import Data.IORef++data Debuggee = Debuggee { -- Keep track of how many of each request we make+                           debuggeeRequestCount :: IORef (HM.HashMap CommandId Int)+                         , debuggeeBatchMode :: BatchMode+                         }++data BatchMode = Batch | OneByOne deriving (Eq, Show)++instance DebugMonad (GenHaxl Debuggee String) where+  type DebugEnv (GenHaxl Debuggee String) = Env Debuggee String+  request = dataFetch+  requestBlock = dataFetch+  traceMsg = tellWrite+  printRequestLog = traceRequestLog+  runDebug = runHaxl+  runDebugTrace = runHaxlWithWrites+  newEnv args =+    case args of+      SnapshotMode _e -> error "Loading from snapshot not supported"+      SocketMode h -> mkEnv h++  saveCache = error "TODO"+  loadCache = error "TODO"+  unsafeLiftIO = error "TODO"++type DebugM = GenHaxl Debuggee String+++{- MP:+- In some profiles it seemed that the caching step was causing quite a bit+- of overhead, but still using the cache is about 2-3x faster than without+- a cache. (ie using `doRequest` directly or `uncachedRequest`.+-}++-- | Send a request to a 'Debuggee' paused with 'withPause'.+--request :: (Show resp, Typeable resp) => Request resp -> DebugM resp+--request = dataFetch+++instance StateKey Request where+  data State Request = RequestState (MVar Handle)++instance DataSourceName Request where+  dataSourceName Proxy = "ghc-debug"++instance ShowP Request where+  showp = show++{-+-- | Group together RequestClosures and RequestInfoTables to avoid+-- some context switching.+groupFetches :: MVar Handle -> [([ClosurePtr], ResultVar [RawClosure])] -> [([InfoTablePtr], ResultVar [(StgInfoTableWithPtr, RawInfoTable)])] -> [BlockedFetch Request] -> [BlockedFetch Request] -> IO ()+groupFetches h cs is todo [] = dispatch h cs is (reverse todo)+groupFetches h cs is todo (b@(BlockedFetch r resp) : bs) =+  case r of+    RequestInfoTables is' -> groupFetches h cs ((is', resp):is) todo bs+    RequestClosures cs' -> groupFetches h ((cs', resp):cs) is todo bs+    _ -> groupFetches h cs is (b:todo) bs++dispatch :: MVar Handle+         -> [([ClosurePtr], ResultVar [RawClosure])]+         -> [([InfoTablePtr], ResultVar [(StgInfoTableWithPtr, RawInfoTable)])]+         -> [BlockedFetch Request]+         -> IO ()+dispatch h cs its other = do+  mapM_ do_one other+  -- These can be used to inspect how much batching is happening+--  print ("BATCHED_CLOSURES", length cs, map fst cs)+--  print (length its, map fst its)+  do_many RequestClosures cs+  do_many RequestInfoTables its+  where+    do_one (BlockedFetch req resp) = do+      res <- doRequest h req+      putSuccess resp res++    do_many :: ([a] -> Request [b]) -> [([a], ResultVar [b])] -> IO ()+    do_many _ [] = return ()+    do_many mk_req ms = do+      let req = mk_req (concatMap fst ms)+      results <- doRequest h req+      recordResults results ms++++-- | Write the correct number of results to each result var+recordResults :: [a] -> [([b], ResultVar [a])] -> IO ()+recordResults [] [] = return ()+recordResults res ((length -> n, rvar):xs) =+  putSuccess rvar here >> recordResults later xs+  where+    (here, later) = splitAt n res+recordResults _ _ = error ("Impossible recordResults")+-}++_singleFetches :: MVar Handle -> [BlockedFetch Request] -> IO ()+_singleFetches h bs = mapM_ do_one bs+      where+        do_one (BlockedFetch req resp) = do+          res <- doRequest h req+          putSuccess resp res++instance DataSource Debuggee Request where+  fetch (RequestState h) _fs u =+    -- Grouping together fetches only shaves off about 0.01s on the simple+    -- benchmark+    SyncFetch $+      case debuggeeBatchMode u of+        --Batch -> groupFetches h [] [] []+        _ -> _singleFetches h++++instance StateKey BlockCacheRequest where+  data State BlockCacheRequest = BCRequestState (IORef BlockCache) (MVar Handle)++instance DataSourceName BlockCacheRequest where+  dataSourceName Proxy = "block-cache"++instance ShowP BlockCacheRequest where+  showp = show+++instance DataSource u BlockCacheRequest where+  fetch (BCRequestState ref h) _fs _u =+    SyncFetch (mapM_ do_one)+    where+      do_one :: BlockedFetch BlockCacheRequest -> IO ()+      do_one (BlockedFetch bcr resp) = do+        res <- handleBlockReq (doRequest h) ref bcr+        putSuccess resp res++++mkEnv :: Handle -> IO (Env Debuggee String)+mkEnv hdl = do+  requestMap <- newIORef HM.empty+  bc <- newIORef emptyBlockCache+  mhdl <- newMVar hdl+  let ss = stateSet (BCRequestState bc mhdl) (stateSet (RequestState mhdl) stateEmpty)+  new_env <- initEnv ss (Debuggee requestMap Batch)+  -- Turn on data fetch stats with report = 3+  let new_flags = defaultFlags { report = 0 }+  return $ new_env { Haxl.Core.flags = new_flags }++-- | Print out the number of request made for each request type+traceRequestLog :: Env u w -> IO ()+traceRequestLog d = do+  s <- readIORef (statsRef d)+  putStrLn (ppStats s)++_traceProfile :: Env u w -> IO ()+_traceProfile e = do+  p <- readIORef (profRef e)+  print (profile p)
+ src/GHC/Debug/Client/Monad/Simple.hs view
@@ -0,0 +1,186 @@+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE RecordWildCards #-}+-- | This module provides a simple implementation, which can be a lot faster if+-- network latency is not an issue.+module GHC.Debug.Client.Monad.Simple+  ( Debuggee+  , DebugM(..)+  , runSimple+  ) where++import Control.Concurrent+import GHC.Debug.Types+import qualified Data.HashMap.Strict as HM+import System.IO+import Data.IORef+import Data.List+import Data.Ord++import GHC.Debug.Client.BlockCache+import GHC.Debug.Client.RequestCache+import GHC.Debug.Client.Monad.Class++import Control.Monad.Fix+import Control.Monad.Reader+import Data.Binary+--import Debug.Trace+++data Debuggee = Debuggee { -- Keep track of how many of each request we make+                           debuggeeRequestCount :: Maybe (IORef (HM.HashMap CommandId FetchStats))+                         , debuggeeBlockCache :: IORef BlockCache+                         , debuggeeRequestCache :: MVar RequestCache+                         , debuggeeHandle :: Maybe (MVar Handle)+                         }++data FetchStats = FetchStats { _networkRequests :: !Int, _cachedRequests :: !Int }++logRequestIO :: Bool -> IORef (HM.HashMap CommandId FetchStats) -> Request resp -> IO ()+logRequestIO cached hmref req =+  atomicModifyIORef' hmref ((,()) . HM.alter alter_fn (requestCommandId req))++  where+    alter_fn = Just . maybe emptyFetchStats upd_fn+    emptyFetchStats = FetchStats 1 0+    upd_fn (FetchStats nr cr)+      | cached = FetchStats nr (cr + 1)+      | otherwise = FetchStats (nr + 1) cr++logRequest :: Bool -> Request resp -> ReaderT Debuggee IO ()+logRequest cached req = do+  mhm <- asks debuggeeRequestCount+  case mhm of+    Just hm -> liftIO $ logRequestIO cached hm req+    Nothing -> return ()++ppRequestLog :: HM.HashMap CommandId FetchStats -> String+ppRequestLog hm = unlines (map row items)+  where+    row (cid, FetchStats net cache) = unwords [show cid ++ ":", show net, show cache]+    items = sortBy (comparing fst) (HM.toList hm)++data Snapshot = Snapshot {+                    _version :: Word32+                  , _rqc :: RequestCache+                  }++snapshotVersion :: Word32+snapshotVersion = 0++instance Binary Snapshot where+  get = do+    v <- get+    if v == snapshotVersion+      then Snapshot v <$> get+      else fail ("Wrong snapshot version.\nGot: " ++ show v ++ "\nExpected: " ++ show snapshotVersion)+  put (Snapshot v c1) = do+    put v+    put c1+++instance DebugMonad DebugM where+  type DebugEnv DebugM = Debuggee+  request = DebugM . simpleReq+  requestBlock = blockReq+  traceMsg = DebugM . liftIO . putStrLn+  printRequestLog e = do+    case debuggeeRequestCount e of+      Just hm_ref -> do+        readIORef hm_ref >>= putStrLn . ppRequestLog+      Nothing -> putStrLn "No request log in Simple(TM) mode"+  runDebug = runSimple+  runDebugTrace e a = (,[]) <$> runDebug e a+  newEnv m = case m of+               SnapshotMode f -> mkSnapshotEnv f+               SocketMode h -> mkHandleEnv h++  loadCache fp = DebugM $ do+    (Snapshot _ new_req_cache) <- lift $ decodeFile fp+    Debuggee{..} <- ask+    _old_rc <- lift $ swapMVar debuggeeRequestCache new_req_cache+    -- Fill up the block cache with the cached blocks+    let block_c = initBlockCacheFromReqCache new_req_cache+    lift $ writeIORef debuggeeBlockCache block_c++  saveCache fp = DebugM $ do+    Debuggee{..} <- ask+    Just req_cache <- lift $ tryReadMVar debuggeeRequestCache+    lift $ encodeFile fp (Snapshot snapshotVersion req_cache)++  unsafeLiftIO f = DebugM $ liftIO f+++initBlockCacheFromReqCache :: RequestCache -> BlockCache+initBlockCacheFromReqCache new_req_cache  =+  case lookupReq RequestAllBlocks new_req_cache of+        Just bs -> addBlocks bs emptyBlockCache+        Nothing -> emptyBlockCache++++runSimple :: Debuggee -> DebugM a -> IO a+runSimple d (DebugM a) = runReaderT a d++mkEnv :: (RequestCache, BlockCache) -> Maybe Handle -> IO Debuggee+mkEnv (req_c, block_c) h = do+  let enable_stats = False+  mcount <- if enable_stats then Just <$> newIORef HM.empty else return Nothing+  bc <- newIORef  block_c+  rc <- newMVar req_c+  mhdl <-  traverse newMVar h+  return $ Debuggee mcount bc rc mhdl++mkHandleEnv :: Handle -> IO Debuggee+mkHandleEnv h = mkEnv (emptyRequestCache, emptyBlockCache) (Just h)++mkSnapshotEnv :: FilePath -> IO Debuggee+mkSnapshotEnv fp = do+  Snapshot _ req_c <- decodeFile fp+  let block_c = initBlockCacheFromReqCache req_c+  mkEnv (req_c, block_c) Nothing++-- TODO: Sending multiple pauses will clear the cache, should keep track of+-- the pause state and only clear caches if the state changes.+simpleReq :: Request resp -> ReaderT Debuggee IO resp+simpleReq req | isWriteRequest req = ask >>= \Debuggee{..} -> liftIO $ withWriteRequest req (error "non-write") $ \wreq -> do+  case debuggeeHandle of+    Just h -> do+      atomicModifyIORef' debuggeeBlockCache (const (emptyBlockCache, ()))+      modifyMVar_ debuggeeRequestCache (return . clearMovableRequests)+      doRequest h wreq+    -- Ignore write requests in snapshot mode+    Nothing -> return ()+simpleReq req = do+  rc_var <- asks debuggeeRequestCache+  rc <- liftIO $ readMVar rc_var+  case lookupReq req rc of+    Just res -> do+      logRequest True req+      return res+    Nothing -> do+      mh <- asks debuggeeHandle+      case mh of+        Nothing -> error ("Cache Miss:" ++ show req)+        Just h -> do+          res <- liftIO $ doRequest h req+          liftIO $ modifyMVar_ rc_var (return . cacheReq req res)+          logRequest False req+          return res++blockReq :: BlockCacheRequest resp -> DebugM resp+blockReq req = DebugM $ do+  bc  <- asks debuggeeBlockCache+  env <- ask+  liftIO $ handleBlockReq (\r -> runReaderT (simpleReq r) env) bc req++newtype DebugM a = DebugM (ReaderT Debuggee IO a)+                   -- Only derive the instances that DebugMonad needs+                    deriving (MonadFail, Functor, Applicative, Monad, MonadFix)++
+ src/GHC/Debug/Client/Query.hs view
@@ -0,0 +1,185 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DerivingVia #-}+module GHC.Debug.Client.Query+  ( -- * Pause/Resume+    pause+  , fork+  , pauseThen+  , resume+  , pausePoll+  , withPause++  -- * General Requests+  , precacheBlocks+  , gcRoots+  , allBlocks+  , getSourceInfo+  , savedObjects+  , version++  -- * Dereferencing functions+  , dereferenceClosures+  , dereferenceClosure+  , dereferenceClosureDirect+  , dereferenceClosureC+  , dereferenceStack+  , dereferencePapPayload+  , dereferenceConDesc+  , dereferenceInfoTable++  ) where++import           Control.Exception+import           GHC.Debug.Types+import           GHC.Debug.Decode+import           GHC.Debug.Decode.Stack+import GHC.Debug.Client.Monad+import           GHC.Debug.Client.BlockCache+import Control.Monad.State+import Data.Word++import Debug.Trace++-- | Pause the debuggee+pause :: Debuggee -> IO ()+pause e = do+  run e $ request (RequestPause Pause)++fork :: Debuggee -> IO ()+fork e = do+  run e $ request (RequestPause Fork)++-- | Resume the debuggee+resume :: Debuggee -> IO ()+resume e = run e $ request RequestResume++-- | Like pause, but wait for the debuggee to pause itself. It currently+-- impossible to resume after a pause caused by a poll.?????????? Is that true???? can we not just call resume????+pausePoll :: Debuggee -> IO ()+pausePoll e = do+  run e $ request RequestPoll++-- | Bracketed version of pause/resume.+withPause :: Debuggee -> IO a -> IO a+withPause dbg act = bracket_ (pause dbg) (resume dbg) act+++lookupInfoTable :: RawClosure -> DebugM (StgInfoTableWithPtr, RawInfoTable, RawClosure)+lookupInfoTable rc = do+    let ptr = getInfoTblPtr rc+    (itbl, rit) <- request (RequestInfoTable ptr)+    return (itbl,rit, rc)++pauseThen :: Debuggee -> DebugM b -> IO b+pauseThen e d =+  pause e >> run e d+++dereferenceClosureC :: ClosurePtr -> DebugM SizedClosureC+dereferenceClosureC cp = do+  c <- dereferenceClosure cp+  quadtraverse pure dereferenceConDesc pure pure c++-- | Decode a closure corresponding to the given 'ClosurePtr'+-- You should not use this function directly unless you know what you are+-- doing. 'dereferenceClosure' will be much faster in general.+dereferenceClosureDirect :: ClosurePtr -> DebugM SizedClosure+dereferenceClosureDirect c = do+    raw_c <- request (RequestClosure c)+    let it = getInfoTblPtr raw_c+    raw_it <- request (RequestInfoTable it)+    return $ decodeClosure raw_it (c, raw_c)++dereferenceClosures  :: [ClosurePtr] -> DebugM [SizedClosure]+dereferenceClosures cs = mapM dereferenceClosure cs++-- | Deference some StackFrames from a given 'StackCont'+dereferenceStack :: StackCont -> DebugM StackFrames+dereferenceStack (StackCont sp stack) = do+--  req_stack <- request (RequestStack (coerce cp))+  let get_bitmap o = request (RequestStackBitmap sp o)+      get_info_table rc = (\(a, _, _) -> a) <$> lookupInfoTable rc+--  traceShowM ("BAD", printStack stack, rawStackSize stack)+--  traceShowM ("GOOD", printStack req_stack, rawStackSize req_stack)+  decodeStack get_info_table get_bitmap stack++-- | Derference the PapPayload from the 'PayloadCont'+dereferencePapPayload :: PayloadCont -> DebugM PapPayload+dereferencePapPayload (PayloadCont fp raw) = do+  bm <- request (RequestFunBitmap (fromIntegral $ length raw) fp)+  return $ GenPapPayload (evalState (traversePtrBitmap decodeField bm) raw)+  where+    getWord = do+      v <- gets head+      modify tail+      return v++    decodeField True  = SPtr . mkClosurePtr <$> getWord+    decodeField False = SNonPtr <$> getWord+++dereferenceConDesc :: ConstrDescCont -> DebugM ConstrDesc+dereferenceConDesc i = request (RequestConstrDesc i)++_noConDesc :: ConstrDescCont -> DebugM ConstrDesc+_noConDesc c = traceShow c (return emptyConDesc)++emptyConDesc :: ConstrDesc+emptyConDesc = ConstrDesc "" "" ""++{-+-- | Print out the number of request made for each request type+traceRequestLog :: Env u w -> IO ()+traceRequestLog d = do+  s <- readIORef (statsRef d)+  putStrLn (ppStats s)++traceProfile :: Env u w -> IO ()+traceProfile e = do+  p <- readIORef (profRef e)+  print (profile p)+  -}++-- | Consult the 'BlockCache' for the block which contains a specific+-- closure, if it's not there then try to fetch the right block, if that+-- fails, call 'dereferenceClosureDirect'+dereferenceClosure :: ClosurePtr -> DebugM SizedClosure+dereferenceClosure cp+  | not (heapAlloced cp) = dereferenceClosureDirect cp+  | otherwise = do+      rc <-  requestBlock (LookupClosure cp)+      let it = getInfoTblPtr rc+      st_it <- request (RequestInfoTable it)+      return $ decodeClosure st_it (cp, rc)++-- | Fetch all the blocks from the debuggee and add them to the block cache+precacheBlocks :: DebugM [RawBlock]+precacheBlocks = requestBlock PopulateBlockCache++-- | Query the debuggee for the list of GC Roots+gcRoots :: DebugM [ClosurePtr]+gcRoots = request RequestRoots++-- | Query the debuggee for all the blocks it knows about+allBlocks :: DebugM [RawBlock]+allBlocks = request RequestAllBlocks++-- | Query the debuggee for source information about a specific info table.+-- This requires your executable to be built with @-finfo-table-map@.+getSourceInfo :: InfoTablePtr -> DebugM (Maybe SourceInformation)+getSourceInfo = request . RequestSourceInfo++-- | Query the debuggee for the list of saved objects.+savedObjects :: DebugM [ClosurePtr]+savedObjects = request RequestSavedObjects++-- | Query the debuggee for the protocol version+version :: DebugM Word32+version = request RequestVersion++dereferenceInfoTable :: InfoTablePtr -> DebugM StgInfoTable+dereferenceInfoTable it = decodedTable . fst <$> request (RequestInfoTable it)+
+ src/GHC/Debug/Client/RequestCache.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE GADTs #-}+module GHC.Debug.Client.RequestCache(RequestCache+                                    , cacheReq+                                    , lookupReq+                                    , emptyRequestCache+                                    , clearMovableRequests+                                    , putCache+                                    , getCache ) where++import qualified Data.HashMap.Strict as HM+import GHC.Debug.Types+import Unsafe.Coerce+import Data.Binary+import Control.Monad+import Data.Binary.Put+import Data.Binary.Get++newtype RequestCache = RequestCache (HM.HashMap AnyReq AnyResp)++instance Binary RequestCache where+  get = getCache+  put = putCache++cacheReq :: Request resp -> resp -> RequestCache -> RequestCache+cacheReq req resp (RequestCache rc)+  -- Don't cache the results of writes, such as pause/unpause+  | isWriteRequest req = RequestCache rc+  | otherwise = RequestCache (HM.insert (AnyReq req) (AnyResp resp (putResponseBinary req)) rc)++lookupReq :: forall resp . Request resp -> RequestCache -> Maybe resp+lookupReq req (RequestCache rc) = coerceResult <$> HM.lookup (AnyReq req) rc+  where+    coerceResult :: AnyResp -> resp+    coerceResult (AnyResp a _) = unsafeCoerce a++emptyRequestCache :: RequestCache+emptyRequestCache = RequestCache HM.empty++-- These get/put functions are a lot like the ones for serialising info+-- to/from the debuggee but we are careful that each one reads a bounded+-- amount of input.++getResponseBinary :: Request a -> Get a+getResponseBinary RequestVersion       = getWord32be+getResponseBinary (RequestPause {})    = get+getResponseBinary RequestResume        = get+getResponseBinary RequestRoots         = get+getResponseBinary (RequestClosure {}) = get+getResponseBinary (RequestInfoTable itps) =+      (\(it, r) -> (StgInfoTableWithPtr itps it, r)) <$> getInfoTable+getResponseBinary (RequestStackBitmap {}) = get+getResponseBinary (RequestFunBitmap {}) = get+getResponseBinary (RequestConstrDesc _)  = getConstrDescCache+getResponseBinary RequestPoll          = get+getResponseBinary RequestSavedObjects  = get+getResponseBinary (RequestSourceInfo _c) = getIPE+getResponseBinary RequestAllBlocks = get+getResponseBinary RequestBlock {}  = get++putResponseBinary :: Request a -> a -> Put+putResponseBinary RequestVersion w = putWord32be w+putResponseBinary (RequestPause {}) w  = put w+putResponseBinary RequestResume w      = put w+putResponseBinary RequestRoots  rs     = put rs+putResponseBinary (RequestClosure {}) rcs = put rcs+putResponseBinary (RequestInfoTable {}) (_, r) = putInfoTable r+putResponseBinary (RequestStackBitmap {}) pbm = put pbm+putResponseBinary (RequestFunBitmap {}) pbm = put pbm+putResponseBinary (RequestConstrDesc _) cd  = putConstrDescCache cd+putResponseBinary RequestPoll         r = put r+putResponseBinary RequestSavedObjects os = putList os+putResponseBinary (RequestSourceInfo _c) ipe = putIPE ipe+putResponseBinary RequestAllBlocks rs = put rs+putResponseBinary RequestBlock {} r = put r++putConstrDescCache :: ConstrDesc -> Put+putConstrDescCache (ConstrDesc a b c) = do+  put a+  put b+  put c++getConstrDescCache :: Get ConstrDesc+getConstrDescCache =+  ConstrDesc <$> get <*> get <*> get++putLine :: AnyReq -> AnyResp -> Put -> Put+putLine (AnyReq req) (AnyResp resp p) k = putRequest req >> p resp >> k++getCacheLine :: Get (AnyReq, AnyResp)+getCacheLine = do+  AnyReq req <- getRequest+  resp <- getResponseBinary req+  return (AnyReq req, AnyResp resp (putResponseBinary req))++putCache :: RequestCache -> Put+putCache (RequestCache rc) = do+  put (HM.size rc)+  HM.foldrWithKey putLine (return ()) rc++getCache :: Get RequestCache+getCache = do+  n <- get+  RequestCache . HM.fromList <$> replicateM n getCacheLine++-- | Clear the part of the cache which will become invalid after pausing+-- For example, we need to clear blocks, but can keep the info table+-- caches.+clearMovableRequests :: RequestCache -> RequestCache+clearMovableRequests (RequestCache rc) = RequestCache (HM.filterWithKey (\(AnyReq r) _ -> isImmutableRequest r) rc)
+ src/GHC/Debug/Client/Search.hs view
@@ -0,0 +1,25 @@+module GHC.Debug.Client.Search(module GHC.Debug.Client.Search, HeapGraph(..), HeapGraphEntry(..)) where++import GHC.Debug.Types+import GHC.Debug.Types.Graph+import qualified Data.IntMap as IM++-- Find all entries in the HeapGraph matching a specific predicate+findClosures :: (HeapGraphEntry a -> Bool) -> HeapGraph a -> [HeapGraphEntry a]+findClosures f = go+  where+    go (HeapGraph _ gs) =+      IM.foldl' (\hges hge -> if f hge then hge:hges else hges) [] gs++findConstructors :: String -> HeapGraph a -> [HeapGraphEntry a]+findConstructors con_name hg = findClosures predicate hg+    where+      predicate h = checkConstrTable (hgeClosure h)++      checkConstrTable (ConstrClosure _ _ _ (ConstrDesc _ _ n)) = n == con_name+      checkConstrTable _ = False++findWithInfoTable :: InfoTablePtr -> HeapGraph a -> [HeapGraphEntry a]+findWithInfoTable itp = findClosures p+  where+    p = (itp ==) . tableId . info . hgeClosure
+ src/GHC/Debug/Count.hs view
@@ -0,0 +1,46 @@+module GHC.Debug.Count where++import           GHC.Debug.Types+import GHC.Debug.Client.Monad+import           GHC.Debug.Profile+import           GHC.Debug.Trace+import           GHC.Debug.ParTrace hiding (TraceFunctionsIO(..))+import GHC.Debug.ParTrace (TraceFunctionsIO(TraceFunctionsIO))+import Control.Monad.State+++parCount :: [ClosurePtr] -> DebugM CensusStats+parCount = traceParFromM funcs . map (ClosurePtrWithInfo ())+  where+    nop = const (return ())+    funcs = TraceFunctionsIO nop nop clos (const (const (return mempty))) nop++    clos :: ClosurePtr -> SizedClosure -> ()+              -> DebugM ((), CensusStats, DebugM () -> DebugM ())+    clos _cp sc _ = do+      return ((), mkCS (dcSize sc), id)++-- | Simple statistics about a heap, total objects, size and maximum object+-- size+count :: [ClosurePtr] -> DebugM CensusStats+count cps = snd <$> runStateT (traceFromM funcs cps) (CS 0 0 0)+  where+    funcs = TraceFunctions {+               papTrace = const (return ())+              , stackTrace = const (return ())+              , closTrace = closAccum+              , visitedVal = const (return ())+              , conDescTrace = const (return ())++            }++    closAccum  :: ClosurePtr+               -> SizedClosure+               ->  (StateT CensusStats DebugM) ()+               ->  (StateT CensusStats DebugM) ()+    closAccum _cp s k = do+      modify' (go s)+      k++    go :: SizedClosure -> CensusStats -> CensusStats+    go sc cs = mkCS (dcSize sc) <> cs
+ src/GHC/Debug/Dominators.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ScopedTypeVariables #-}+module GHC.Debug.Dominators (computeDominators+                                   , retainerSize+                                   , convertToHeapGraph+                                   , annotateWithRetainerSize ) where++import Data.Maybe       ( catMaybes, fromJust )+import qualified Data.IntMap as IM+import qualified Data.IntSet as IS+import GHC.Debug.Types.Ptr+import GHC.Debug.Types.Closures+import qualified Data.List.NonEmpty as NE+import qualified Data.Foldable as F+import qualified Data.Graph.Dom as DO+import qualified Data.Tree as Tree+import GHC.Debug.Types.Graph++++-- Dominators+closurePtrToInt :: ClosurePtr -> Int+closurePtrToInt (ClosurePtr p) = fromIntegral p++intToClosurePtr :: Int -> ClosurePtr+intToClosurePtr i = mkClosurePtr (fromIntegral i)++convertToDom :: HeapGraph a -> DO.Rooted+convertToDom  (HeapGraph groots is) = (0, new_graph)+  where+    rootNodes = IS.fromList (map closurePtrToInt (NE.toList groots))+    new_graph = IM.insert 0 rootNodes (IM.foldlWithKey' collectNodes IM.empty is)+    collectNodes newMap k h =  IM.insert k (IS.fromList (map closurePtrToInt (catMaybes (allClosures (hgeClosure h))))) newMap++computeDominators :: HeapGraph a -> [Tree.Tree (HeapGraphEntry a)]+computeDominators hg = map (fmap (fromJust . flip lookupHeapGraph hg . intToClosurePtr)) gentries+  where+    gentries = case DO.domTree (convertToDom hg) of+                Tree.Node 0 es -> es+                _ -> error "Dominator tree must contain 0"++retainerSize :: HeapGraph Size -> [Tree.Tree (HeapGraphEntry (Size, RetainerSize))]+retainerSize hg = map bottomUpSize doms+  where+    doms = computeDominators hg++annotateWithRetainerSize :: HeapGraph Size -> HeapGraph (Size, RetainerSize)+annotateWithRetainerSize h@(HeapGraph rs _) =+  HeapGraph rs (foldMap convertToHeapGraph (retainerSize h))++bottomUpSize :: Tree.Tree (HeapGraphEntry Size) -> Tree.Tree (HeapGraphEntry (Size, RetainerSize))+bottomUpSize (Tree.Node rl sf) =+  let ts = map bottomUpSize sf+      s'@(Size s) =  hgeData rl+      RetainerSize children_size = foldMap (snd . hgeData . Tree.rootLabel) ts+      inclusive_size :: RetainerSize+      !inclusive_size = RetainerSize  (s + children_size)+      rl' = rl { hgeData = (s', inclusive_size) }+  in Tree.Node rl' ts++convertToHeapGraph ::  Tree.Tree (HeapGraphEntry a) -> IM.IntMap (HeapGraphEntry a)+convertToHeapGraph t = IM.fromList ([(fromIntegral cp, c) | c <- F.toList t, let ClosurePtr cp = hgeClosurePtr c ])+++
+ src/GHC/Debug/Fragmentation.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+-- | Functions for analysing memory fragmentation+module GHC.Debug.Fragmentation (summariseBlocks+                                     , censusByMBlock+                                      , printMBlockCensus+                                      , censusByBlock+                                      , printBlockCensus+                                      , censusPinnedBlocks+                                      , PinnedCensusStats(..)++                                      , findBadPtrs+                                      , histogram+                                      ) where++import GHC.Debug.Profile+import GHC.Debug.Client+import GHC.Debug.Types+--import GHC.Debug.Client.Monad++import qualified Data.Map as Map+import qualified Data.Set as Set+import Data.List (nub, sortBy)+import Data.Ord+import Data.Word++-- | Print a summary of the given raw blocks+-- This is useful to see how many MBlocks and how many pinned blocks there+-- are.+summariseBlocks :: [RawBlock] -> IO ()+summariseBlocks bs = do+  putStrLn ("TOTAL BLOCKS: " ++ show (length bs))+  putStrLn ("PINNED BLOCKS: " ++ show (length $ filter isPinnedBlock bs))+  putStrLn ("MBLOCK: " ++ show n_mblocks)+  putStrLn ("PINNED MBLOCKS: " ++ show n_pmblocks)+  where+    n_mblocks :: Int+    n_mblocks = length (nub (map (blockMBlock . rawBlockAddr) bs))++    n_pmblocks = length (nub (map (blockMBlock . rawBlockAddr) (filter isPinnedBlock bs)))++-- | Perform a heap census by which MBlock each closure lives in+censusByMBlock :: [ClosurePtr] -> DebugM (Map.Map BlockPtr CensusStats)+censusByMBlock = closureCensusBy go+  where+    go cp d =+      let s :: Size+          s = dcSize d+          v =  mkCS s++          k :: BlockPtr+          k = applyMBlockMask cp+      in if heapAlloced cp+           then return $ Just (k, v)+           -- Ignore static things+           else return $ Nothing++-- | Perform a census based on which block each closure resides in.+censusByBlock :: [ClosurePtr] -> DebugM (Map.Map BlockPtr CensusStats)+censusByBlock = closureCensusBy go+  where+    go cp d =+      let s :: Size+          s = dcSize d+          v =  mkCS s++          k = applyBlockMask cp+      in if heapAlloced cp+           then return $ Just (k, v)+           -- Ignore static things+           else return Nothing++newtype PinnedCensusStats =+          PinnedCensusStats (CensusStats, [(ClosurePtr, SizedClosure)])+          deriving (Semigroup)++-- | Only census the given (pinned) blocks+censusPinnedBlocks :: [RawBlock]+                   -> [ClosurePtr]+                   -> DebugM (Map.Map BlockPtr PinnedCensusStats)+censusPinnedBlocks bs = closureCensusBy go+  where+    pbs = Set.fromList (map rawBlockAddr (filter isPinnedBlock bs))+    go :: ClosurePtr -> SizedClosure+          -> DebugM (Maybe (BlockPtr, PinnedCensusStats))+    go cp d =+      let v :: CensusStats+          v = mkCS (dcSize d)++          bp = applyBlockMask cp++      in return $ if bp `Set.member` pbs+           then Just (bp, PinnedCensusStats (v, [(cp, d)]))+           -- Ignore static things+           else Nothing+++-- | Given a pinned block census, find the ARR_WORDS objects which are in the+-- blocks which are < 10 % utilised. The return list is sorted by how many+-- times each distinct ARR_WORDS appears on the heap.+findBadPtrs :: Map.Map k PinnedCensusStats+            -> [((Count, [ClosurePtr]), String)]+findBadPtrs mb_census  =+      let fragged_blocks = Map.filter (\(PinnedCensusStats (CS _ (Size s) _, _)) -> fromIntegral s / fromIntegral blockMaxSize <= (0.1 :: Double))  mb_census+          all_arr_words :: [(String, (Count, [ClosurePtr]))]+          all_arr_words = concatMap (\(PinnedCensusStats (_, i)) -> map (\(c,d) -> (displayArrWords d, (Count 1, [c]))) i) (Map.elems fragged_blocks)+          swap (a, b) = (b, a)+          dups = map swap (reverse $ sortBy (comparing snd) (Map.toList (Map.fromListWith (<>) all_arr_words)))+      in dups++displayArrWords :: SizedClosure -> String+displayArrWords d =+    case noSize d of+      ArrWordsClosure { arrWords } -> show (arrWordsBS arrWords)+      _ -> error "Not ARR_WORDS"++printMBlockCensus, printBlockCensus ::  Map.Map BlockPtr CensusStats -> IO ()+printMBlockCensus = histogram mblockMaxSize . Map.elems+-- | Print out a block census+printBlockCensus = histogram blockMaxSize . Map.elems++-- | Print either a MBlock or Block census as a histogram+histogram :: Word64 -> [CensusStats] -> IO ()+histogram maxSize m =+  mapM_ (putStrLn . displayLine) (bin 0 (map calcPercentage (sortBy (comparing cssize) m )))+  where+    calcPercentage (CS _ (Size tot) _) =+      ((fromIntegral tot/ fromIntegral maxSize) * 100 :: Double)++    displayLine (l, h, n) = show l ++ "%-" ++ show h ++ "%: " ++ show n++    bin _ [] = []+    bin k xs = case now of+                 [] -> bin (k + 10) later+                 _ -> (k, k+10, length now) : bin (k + 10) later+      where+        (now, later) = span ((<= k + 10)) xs+
+ src/GHC/Debug/GML.hs view
@@ -0,0 +1,120 @@+module GHC.Debug.GML (writeTpfToGML, addSourceInfo, typePointsFromToGML) where++import GHC.Debug.TypePointsFrom as TPF+import GHC.Debug.Types (SourceInformation(..))+import GHC.Debug.Types.Closures (Size(..))+import GHC.Debug.Profile.Types (CensusStats(..), Count(..))+import GHC.Debug.Client.Monad+import GHC.Debug.Client.Query (getSourceInfo, gcRoots)++import Data.Map as Map+import Data.Int (Int32)+import Data.Semigroup+import qualified Data.Map.Monoidal.Strict as MMap+import qualified Data.Foldable as F++import System.IO++type SourceInfoMap = Map.Map TPF.Key SourceInformation++typePointsFromToGML :: FilePath -> Debuggee -> IO ()+typePointsFromToGML path e = do+  (tpf, infoMap) <- run e $ do+    roots <- gcRoots+    tpf <- TPF.typePointsFrom roots+    si <- addSourceInfo tpf+    return (tpf, si)+  writeTpfToGML path tpf infoMap++-- | Exports TypePointsFrom graph to a GML file+addSourceInfo :: TypePointsFrom -> DebugM SourceInfoMap+addSourceInfo tpf = do+    -- Generate a map of InfoTablePtr to SourceInformation pairs+    let ptrs = MMap.keys . TPF.nodes $ tpf+    infos <- mapM getSourceInfo ptrs++    let kvPairs :: [(TPF.Key, SourceInformation)]+        kvPairs = do+          (k, Just si) <- zip ptrs infos+          return (k, si)++        infoMap :: SourceInfoMap+        infoMap = Map.fromList kvPairs++    return (infoMap)+++writeTpfToGML :: FilePath -> TPF.TypePointsFrom -> SourceInfoMap -> IO ()+writeTpfToGML path tpf infoMap = do+  outHandle <- openFile path WriteMode+  writeGML outHandle+  hClose outHandle+  where+    ixMap :: Map.Map TPF.Key Int32+    ixMap = Map.fromList $ zip ((MMap.keys . nodes) tpf) [1..]++    lookupId :: TPF.Key -> Int32+    lookupId key' = case Map.lookup key' ixMap of+      Nothing -> error "This shouldn't happen, see function ixMap"+      Just i -> i++    writeGML :: Handle -> IO ()+    writeGML outHandle = do+      let nodesKvPairs = MMap.assocs . TPF.nodes $ tpf+          edgesKvPairs = MMap.assocs . TPF.edges $ tpf++      -- Beginning of GML file+      hPutStrLn stderr $ "Writing to file " <> path <> "..."+      writeOpenGML++      F.forM_ nodesKvPairs (uncurry writeNode)+      F.forM_ edgesKvPairs (uncurry writeEdge)++      writeCloseGML+      hPutStrLn stderr $ "Finished writing to GML file..."+      -- End of GML file+      where+        write = hPutStr outHandle++        writeOpenGML =+          write $ "graph[\n"+            <> "comment \"this is a graph in GML format\"\n"+            <> "directed 1\n"++        writeCloseGML =+          write $ "]\n"++        writeNode :: TPF.Key -> CensusStats -> IO ()+        writeNode key' cs =+          write $ "node [\n"+            <> "id " <> showPtr key' <> "\n"+            <> gmlShowCensus cs+            <> gmlShowSourceInfo key'+            <> "]\n"++        writeEdge :: TPF.Edge -> CensusStats -> IO ()+        writeEdge edge cs =+          write $ "edge [\n"+            <> "source " <> (showPtr . TPF.edgeSource) edge <> "\n"+            <> "target " <> (showPtr . TPF.edgeTarget) edge <> "\n"+            <> gmlShowCensus cs+            <> "]\n"++        gmlShowCensus :: CensusStats -> String+        gmlShowCensus (CS (Count c) (Size s) (Max (Size m))) =+          "count " <> show c <> "\n"+          <> "size " <> show s <> "\n"+          <> "max " <> show m <> "\n"++        gmlShowSourceInfo :: TPF.Key -> String+        gmlShowSourceInfo key = case Map.lookup key infoMap of+          Nothing -> mempty+          Just si -> "infoName \"" <> infoName si <> "\"\n"+            <> "infoClosureType \"" <> (show . infoClosureType) si <> "\"\n"+            <> "infoType \"" <> infoType si <> "\"\n"+            <> "infoLabel \"" <> infoLabel si <> "\"\n"+            <> "infoModule \"" <> infoModule si <> "\"\n"+            <> "infoPosition \"" <> infoPosition si <> "\"\n"++        showPtr :: TPF.Key -> String+        showPtr = show . lookupId
+ src/GHC/Debug/ObjectEquiv.hs view
@@ -0,0 +1,178 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE ViewPatterns #-}+-- | Attempt to find duplicate objects on the heap. The analysis is not+-- exact but attempts to find closures which are identical, and could be+-- shared.+module GHC.Debug.ObjectEquiv(objectEquiv, objectEquivAnalysis, printObjectEquiv, EquivMap) where++import GHC.Debug.Client.Monad+import GHC.Debug.Client+import GHC.Debug.Trace+import GHC.Debug.Profile+import GHC.Debug.Types.Graph (ppClosure)+import GHC.Debug.Types(ClosurePtr(..))++import Control.Monad.State+import Data.List (sortBy)+import Data.Ord+import Debug.Trace+import qualified Data.OrdPSQ as PS+import qualified Data.IntMap.Strict as IM+import Data.List.NonEmpty(NonEmpty(..))++type CensusByObjectEquiv = IM.IntMap CensusStats++-- | How big to allow the priority queue to grow to+limit :: Int+limit = 100_000+-- | How many times an object must appear per 100 000 closures to be+-- "interesting" and kept for the future.+of_interest :: Int+of_interest = 1000++-- Pick a representative ClosurePtr for each object+type EquivMap = PS.OrdPSQ PtrClosure -- Object+                           Int+                           ClosurePtr -- Representative of equivalence class++type Equiv2Map = IM.IntMap -- Pointer+                  ClosurePtr -- Representative of equivalence class++data ObjectEquivState = ObjectEquivState  {+                            emap   :: !EquivMap+                          , emap2 :: !Equiv2Map+                          , _census :: !CensusByObjectEquiv+                          }+-- Don't need to add identity mapping in emap2 because lookup failure is+-- the identity anyway.+addEquiv :: ClosurePtr -> PtrClosure -> ObjectEquivState -> ObjectEquivState+addEquiv cp pc (trimMap -> o) =+                  let (res, new_m) = PS.alter g pc (emap o)+                      new_emap2 = case res of+                                     Left _ -> emap2 o+                                     -- Only remap objects which have a hit+                                     -- in the cache+                                     Right new_cp -> addNewMap cp new_cp (emap2 o)+                  in ( o { emap = new_m+                      , emap2 = new_emap2 })+  where+    g Nothing = (Left cp, Just (0, cp))+    g (Just (p, v)) = (Right v, Just (p + 1, v))++addNewMap :: ClosurePtr -> ClosurePtr -> Equiv2Map -> Equiv2Map+addNewMap (ClosurePtr cp) equiv_cp o = IM.insert (fromIntegral cp) equiv_cp o++-- Trim down map to keep objects which have only been seen 100 times or+-- more within the last 10 000 seen objects+trimMap :: ObjectEquivState -> ObjectEquivState+trimMap o = if checkSize o > limit+              then let new_o = o { emap = snd $ PS.atMostView of_interest (emap o) }+                   in traceShow (checkSize new_o) new_o+                   -- TODO: Here would be good to also keep everything+                   -- which is referenced by the kept closures, otherwise+                   -- you end up with duplicates in the map++              else o++-- | O(1) due to psqueues implementation+checkSize :: ObjectEquivState -> Int+checkSize (ObjectEquivState e1 _ _) = PS.size e1++type PtrClosure = DebugClosureWithSize PapPayload ConstrDesc StackFrames ClosurePtr++-- | General function for performing a heap census in constant memory+censusObjectEquiv :: [ClosurePtr] -> DebugM ObjectEquivState+censusObjectEquiv cps = snd <$> runStateT (traceFromM funcs cps) (ObjectEquivState PS.empty IM.empty IM.empty)+  where+    funcs = TraceFunctions {+               papTrace = const (return ())+              , stackTrace = const (return ())+              , closTrace = closAccum+              , visitedVal = const (return ())+              , conDescTrace = const (return ())++            }+    -- Add cos+    closAccum  :: ClosurePtr+               -> SizedClosure+               -> (StateT ObjectEquivState DebugM) ()+               -> (StateT ObjectEquivState DebugM) ()+    closAccum cp s k = do+      -- Step 0: Check to see whether there is already an equivalence class+      -- for this cp+      -- Step 1: Decode a bit more of the object, so we can see all the+      -- pointers.+      s' <- lift $ quadtraverse dereferencePapPayload dereferenceConDesc dereferenceStack pure s+      -- Step 2: Replace all the pointers in the closure by things they are+      -- equivalent to we have already seen.+      s''  <- quadtraverse (traverse rep_c) pure (traverse rep_c) rep_c s'+      -- Step 3: Have we seen a closure like this one before?+      modify' (addEquiv cp s'')++{-+        -- Yes, we have seen something of identical shape+        -- 1. Update equivalence maps to map this closure into the right+        -- equivalence class+        Just new_cp -> do+          n <- checkSize <$> lift get+          traceShowM n+          lift $ modify' (addNewMap cp new_cp)+          return new_cp+          --+        -- No, never seen something like this before+        -- Add the mapping to emap+        Nothing -> do+          lift $ modify' (addNewEquiv cp s'')+          return cp+          -}++      -- Step 4: Update the census, now we know the equivalence class of+      -- the object+      --lift $ modify' (go new_cp s'')+      -- Step 5: Call the continuation to carry on with the analysis+      k++    rep_c cp@(ClosurePtr k) = do+      m <- gets emap2+      case IM.lookup (fromIntegral k) m of+        -- There is an equivalence class already+        Just cp' -> return cp'+        -- No equivalence class yet+        Nothing -> return cp++printObjectEquiv :: EquivMap -> IO ()+printObjectEquiv c = do+  let cmp (_, b,_) = b+      res = sortBy (flip (comparing cmp)) (PS.toList c)+      showLine (k, p, v) =+        concat [show v, ":", show p,":", ppClosure "" (\_ -> show) 0 (noSize k)]+  mapM_ (putStrLn . showLine) res+--  writeFile "profile/profile_out.txt" (unlines $ "key, total, count, max, avg" : (map showLine res))++objectEquivAnalysis :: DebugM (EquivMap, HeapGraph Size)+objectEquivAnalysis = do+  precacheBlocks+  rs <- gcRoots+  traceWrite (length rs)+  r1 <- emap <$> censusObjectEquiv rs+  let elems = snd $ PS.atMostView of_interest r1+      cmp (_, b,_) = b+      cps = map (\(_, _, cp) -> cp) (sortBy (flip (comparing cmp)) (PS.toList elems))+  -- Use this code if we are returning ClosurePtr not SourceInformation+  r2 <- case cps of+    [] -> error "None"+    (c:cs) -> multiBuildHeapGraph (Just 10) (c :| cs)+  return (r1, r2)++objectEquiv :: Debuggee -> IO ()+objectEquiv = runAnalysis objectEquivAnalysis $ \(rmap, hg) -> do+                                                    printObjectEquiv rmap+                                                    putStrLn $ ppHeapGraph show hg
+ src/GHC/Debug/ParTrace.hs view
@@ -0,0 +1,258 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE NumericUnderscores #-}+{-# OPTIONS_GHC -Wno-orphans #-}+-- | Functions to support the constant space traversal of a heap.+-- This module is like the Trace module but performs the tracing in+-- parellel. The speed-up is quite modest but hopefully can be improved in+-- future.+--+-- The tracing functions create a thread for each MBlock which we+-- traverse, closures are then sent to the relevant threads to be+-- dereferenced and thread-local storage is accumulated.+module GHC.Debug.ParTrace ( traceParFromM, tracePar, TraceFunctionsIO(..), ClosurePtrWithInfo(..) ) where++import           GHC.Debug.Types+import           GHC.Debug.Client.Query++import qualified Data.IntMap as IM+import Data.Array.BitArray.IO hiding (map)+import Control.Monad.Reader+import Data.Word+import GHC.Debug.Client.Monad.Simple+import Control.Concurrent.Async+import Data.IORef+import Control.Exception.Base+import Debug.Trace+import Control.Concurrent.STM++threads :: Int+threads = 64++type InChan = TChan+type OutChan = TChan++unsafeLiftIO :: IO a -> DebugM a+unsafeLiftIO = DebugM . liftIO++-- | State local to a thread, there are $threads spawned, each which deals+-- with (address `div` 8) % threads. Each thread therefore:+--+-- * Outer map, segmented by MBlock+--  * Inner map, blocks for that MBlock+--    * Inner IOBitArray, visited information for that block+data ThreadState s = ThreadState (IM.IntMap (IM.IntMap (IOBitArray Word16))) (IORef s)++newtype ThreadInfo a = ThreadInfo (InChan (ClosurePtrWithInfo a))++-- | A 'ClosurePtr' with some additional information which needs to be+-- communicated across to another thread.+data ClosurePtrWithInfo a = ClosurePtrWithInfo !a !ClosurePtr+++-- | Map from Thread -> Information about the thread+type ThreadMap a = IM.IntMap (ThreadInfo a)++newtype TraceState a = TraceState { visited :: (ThreadMap a) }+++getKeyTriple :: ClosurePtr -> (Int, Int, Word16)+getKeyTriple cp =+  let BlockPtr raw_bk = applyBlockMask cp+      bk = fromIntegral raw_bk `div` 8+      offset = getBlockOffset cp `div` 8+      BlockPtr raw_mbk = applyMBlockMask cp+      mbk = fromIntegral raw_mbk `div` 8+  in (mbk, bk, fromIntegral offset)++getMBlockKey :: ClosurePtr -> Int+getMBlockKey cp =+  let BlockPtr raw_bk = applyMBlockMask cp+  -- Not sure why I had to divide this by 4, but I did.+  in (fromIntegral raw_bk `div` fromIntegral mblockMask `div` 4) `mod` threads++sendToChan :: TraceState a -> ClosurePtrWithInfo a -> DebugM ()+sendToChan  ts cpi@(ClosurePtrWithInfo _ cp) = DebugM $ liftIO $ do+  let st = visited ts+      mkey = getMBlockKey cp+  case IM.lookup mkey st of+    Nothing -> error $ "Not enough chans:" ++ show mkey ++ show threads+    Just (ThreadInfo ic) -> atomically $ writeTChan ic cpi++initThread :: Monoid s =>+              Int+           -> TraceFunctionsIO a s+           -> DebugM (ThreadInfo a, STM Bool, (ClosurePtrWithInfo a -> DebugM ()) -> DebugM (Async s))+initThread n k = DebugM $ do+  e <- ask+  ic <- liftIO $ newTChanIO+  let oc = ic+  ref <- liftIO $ newIORef mempty+  worker_active <- liftIO $ newTVarIO True+  let start go = unsafeLiftIO $ async $ runSimple e $ workerThread n k worker_active ref go oc+      finished = do+        active <- not <$> readTVar worker_active+        empty  <- isEmptyTChan ic+        return (active && empty)++  return (ThreadInfo ic, finished, start)++workerThread :: forall s a . Monoid s => Int -> TraceFunctionsIO a s -> TVar Bool -> IORef s -> (ClosurePtrWithInfo a -> DebugM ()) -> OutChan (ClosurePtrWithInfo a) -> DebugM s+workerThread n k worker_active ref go oc = DebugM $ do+  d <- ask+  r <- liftIO $ newIORef (ThreadState IM.empty ref)+  liftIO $ runSimple d (loop r)+  where+    loop r = do+      mcp <- unsafeLiftIO $ try $ do+              atomically $ writeTVar worker_active False+              atomically $ do+                v <- readTChan oc+                writeTVar worker_active True+                return v+      case mcp of+        -- The thread gets blocked on readChan when the work is finished so+        -- when this happens, catch the exception and return the accumulated+        -- state for the thread. Each thread has a reference to all over+        -- threads, so the exception is only raised when ALL threads are+        -- waiting for work.+        Left AsyncCancelled -> do+          unsafeLiftIO $ readIORef ref+        Right cpi -> deref r cpi >> loop r++    deref r (ClosurePtrWithInfo a cp) = do+        m <- unsafeLiftIO $ readIORef r+        do+          (m', b) <- unsafeLiftIO $ checkVisit cp m+          unsafeLiftIO $ writeIORef r m'+          if b+            then do+              s <- visitedVal k cp a+              unsafeLiftIO $ modifyIORef' ref (s <>)+            else do+              sc <- dereferenceClosure cp+              (a', s, cont) <- closTrace k cp sc a+              unsafeLiftIO $ modifyIORef' ref (s <>)+              cont (() <$ quadtraverse (gop r a') gocd (gos r a') (goc r . ClosurePtrWithInfo a') sc)++    goc r c@(ClosurePtrWithInfo _i cp) =+      let mkey = getMBlockKey cp+      in if (mkey == n)+          then deref r c+          else go c++    -- Just do the other dereferencing in the same thread for other closure+    -- types as they are not as common.+    gos r a st = do+      st' <- dereferenceStack st+      stackTrace k st'+      () <$ traverse (goc r . ClosurePtrWithInfo a) st'++    gocd d = do+      cd <- dereferenceConDesc d+      conDescTrace k cd++    gop r a p = do+      p' <- dereferencePapPayload p+      papTrace k p'+      () <$ traverse (goc r . ClosurePtrWithInfo a) p'+++handleBlockLevel :: IM.Key+                    -> Word16+                    -> IM.IntMap (IOBitArray Word16)+                    -> IO (IM.IntMap (IOBitArray Word16), Bool)++handleBlockLevel bk offset m = do+  case IM.lookup bk m of+    Nothing -> do+      na <- newArray (0, fromIntegral (blockMask `div` 8)) False+      writeArray na offset True+      return (IM.insert bk na m, False)+    Just bm -> do+      res <- readArray bm offset+      unless res (writeArray bm offset True)+      return (m, res)++checkVisit :: ClosurePtr -> ThreadState s -> IO (ThreadState s, Bool)+checkVisit cp st = do+  let (mbk, bk, offset) = getKeyTriple cp+      ThreadState v ref = st+  case IM.lookup mbk v of+    Nothing -> do+      (st', res) <- handleBlockLevel bk offset IM.empty+      return (ThreadState (IM.insert mbk st' v) ref, res)+    Just bm -> do+      (st', res) <- handleBlockLevel bk offset bm+      return (ThreadState (IM.insert mbk st' v) ref, res)++++++data TraceFunctionsIO a s =+      TraceFunctionsIO { papTrace :: !(GenPapPayload ClosurePtr -> DebugM ())+      , stackTrace :: !(GenStackFrames ClosurePtr -> DebugM ())+      , closTrace :: !(ClosurePtr -> SizedClosure -> a -> DebugM (a, s, DebugM () -> DebugM ()))+      , visitedVal :: !(ClosurePtr -> a -> DebugM s)+      , conDescTrace :: !(ConstrDesc -> DebugM ())+      }+++-- | A generic heap traversal function which will use a small amount of+-- memory linear in the heap size. Using this function with appropiate+-- accumulation functions you should be able to traverse quite big heaps in+-- not a huge amount of memory.+--+-- The performance of this parralel version depends on how much contention+-- the functions given in 'TraceFunctionsIO' content for the handle+-- connecting for the debuggee (which is protected by an 'MVar'). With no+-- contention, and precached blocks, the workload can be very evenly+-- distributed leading to high core utilisation.+--+-- As performance depends highly on contention, snapshot mode is much more+-- amenable to parrelisation where the time taken for requests is much+-- lower.+traceParFromM :: Monoid s => TraceFunctionsIO a s -> [ClosurePtrWithInfo a] -> DebugM s+traceParFromM k cps = do+  traceM ("SPAWNING: " ++ show threads)+  (init_mblocks, work_actives, start)  <- unzip3 <$> mapM (\b -> do+                                    (ti, working, start) <- initThread b k+                                    return ((fromIntegral b, ti), working, start)) [0 .. threads - 1]+  let ts_map = IM.fromList init_mblocks+      go  = sendToChan (TraceState ts_map)+  as <- sequence (map ($ go) start )+  mapM_ go cps+  unsafeLiftIO $ waitFinish work_actives+  unsafeLiftIO $ mapM_ cancel as+  unsafeLiftIO $ mconcat <$> mapM wait as++waitFinish :: [STM Bool] -> IO ()+waitFinish working = atomically (checkDone working)+  where+    checkDone [] = return ()+    checkDone (x:xs) = do+      b <- x+      -- The variable tracks whether the thread thinks it's finished (no+      -- active work and empty chan)+      if b then checkDone xs else retry++-- | A parellel tracing function.+tracePar :: [ClosurePtr] -> DebugM ()+tracePar = traceParFromM funcs . map (ClosurePtrWithInfo ())+  where+    nop = const (return ())+    funcs = TraceFunctionsIO nop nop clos (const (const (return ()))) nop++    clos :: ClosurePtr -> SizedClosure -> ()+              -> DebugM ((), (), DebugM () -> DebugM ())+    clos _cp sc _ = do+      let itb = info (noSize sc)+      _traced <- getSourceInfo (tableId itb)+      return ((), (), id)++
+ src/GHC/Debug/Profile.hs view
@@ -0,0 +1,211 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE RecordWildCards #-}+{- | Functions for performing whole heap census in the style of the normal+- heap profiling -}+module GHC.Debug.Profile( profile+                        , censusClosureType+                        , census2LevelClosureType+                        , closureCensusBy+                        , CensusByClosureType+                        , writeCensusByClosureType+                        , CensusStats(..)+                        , mkCS+                        , Count(..)+                        , closureToKey ) where++import GHC.Debug.Client.Monad+import GHC.Debug.Client+import GHC.Debug.Trace+import GHC.Debug.ParTrace+import GHC.Debug.Profile.Types++import qualified Data.Map.Strict as Map+import Control.Monad.State+import Data.List (sortBy)+import Data.Ord+import Control.Concurrent+import Eventlog.Types+import Eventlog.Data+import Eventlog.Total+import Eventlog.HtmlTemplate+import Eventlog.Args (defaultArgs)+import Data.Text (pack, Text, unpack)+import Data.Semigroup+import qualified Data.Text as T+import qualified Data.Map.Monoidal.Strict as MMap++++type CensusByClosureType = Map.Map Text CensusStats++-- | Perform a heap census in the same style as the -hT profile.+censusClosureType :: [ClosurePtr] -> DebugM CensusByClosureType+censusClosureType = closureCensusBy go+  where+    go :: ClosurePtr -> SizedClosure+       -> DebugM (Maybe (Text, CensusStats))+    go _ s = do+      d <- quadtraverse pure dereferenceConDesc pure pure s+      let siz :: Size+          siz = dcSize d+          v =  mkCS siz+      return $ Just (closureToKey (noSize d), v)++++closureToKey :: DebugClosure a ConstrDesc c d -> Text+closureToKey d =+  case d of+     ConstrClosure { constrDesc = ConstrDesc a b c }+       -> pack a <> ":" <> pack b <> ":" <> pack c+     _ -> pack (show (tipe (decodedTable (info d))))+++-- | General function for performing a heap census in constant memory+closureCensusBy :: forall k v . (Semigroup v, Ord k)+                => (ClosurePtr -> SizedClosure -> DebugM (Maybe (k, v)))+                -> [ClosurePtr] -> DebugM (Map.Map k v)+closureCensusBy f cps = do+  () <$ precacheBlocks+  MMap.getMonoidalMap <$> traceParFromM funcs (map (ClosurePtrWithInfo ()) cps)+  where+    funcs = TraceFunctionsIO {+               papTrace = const (return ())+              , stackTrace = const (return ())+              , closTrace = closAccum+              , visitedVal = const (const (return MMap.empty))+              , conDescTrace = const (return ())++            }+    -- Add cos+    closAccum  :: ClosurePtr+               -> SizedClosure+               -> ()+               -> DebugM ((), MMap.MonoidalMap k v, a -> a)+    closAccum cp s () = do+      r <- f cp s+      return . (\s' -> ((), s', id)) $ case r of+        Just (k, v) -> MMap.singleton k v+        Nothing -> MMap.empty++-- | Perform a 2-level census where the keys are the type of the closure+-- in addition to the type of ptrs of the closure. This can be used to+-- distinguish between lists of different type for example.+census2LevelClosureType :: [ClosurePtr] -> DebugM CensusByClosureType+census2LevelClosureType cps = snd <$> runStateT (traceFromM funcs cps) Map.empty+  where+    funcs = TraceFunctions {+               papTrace = const (return ())+              , stackTrace = const (return ())+              , closTrace = closAccum+              , visitedVal = const (return ())+              , conDescTrace = const (return ())++            }+    -- Add cos+    closAccum  :: ClosurePtr+               -> SizedClosure+               -> (StateT CensusByClosureType DebugM) ()+               -> (StateT CensusByClosureType DebugM) ()+    closAccum _ s k = do+      s' <- lift $ quadtraverse dereferencePapPayload dereferenceConDesc dereferenceStack pure s+      pts <- lift $ mapM dereferenceClosure (allClosures (noSize s'))+      pts' <- lift $ mapM (quadtraverse pure dereferenceConDesc pure pure) pts+++      modify' (go s' pts')+      k++    go d args =+      let k = closureToKey (noSize d)+          kargs = map (closureToKey . noSize) args+          final_k :: Text+          final_k = k <> "[" <> T.intercalate "," kargs <> "]"+      in Map.insertWith (<>) final_k (mkCS (dcSize d))++{-+-- | Parallel heap census+parCensus :: [RawBlock] -> [ClosurePtr] -> DebugM (Map.Map Text CensusStats)+parCensus bs cs =  do+  MMap.getMonoidalMap <$> (traceParFromM bs funcs (map (ClosurePtrWithInfo ()) cs))++  where+    nop = const (return ())+    funcs = TraceFunctionsIO nop nop clos  (const (const (return mempty))) nop++    clos :: ClosurePtr -> SizedClosure -> ()+              -> DebugM ((), MMap.MonoidalMap Text CensusStats, DebugM () -> DebugM ())+    clos _cp sc () = do+      d <- quadtraverse pure dereferenceConDesc pure pure sc+      let s :: Size+          s = dcSize sc+          v =  mkCS s+      return $ ((), MMap.singleton (closureToKey (noSize d)) v, id)+      -}+++writeCensusByClosureType :: FilePath -> CensusByClosureType -> IO ()+writeCensusByClosureType outpath c = do+  let res = sortBy (flip (comparing (cssize . snd))) (Map.toList c)+      showLine (k, CS (Count n) (Size s) (Max (Size mn))) =+        concat [unpack k, ":", show s,":", show n, ":", show mn,":", show @Double (fromIntegral s / fromIntegral n)]+  writeFile outpath (unlines $ "key, total, count, max, avg" : map showLine res)+++-- | Peform a profile at the given interval (in seconds), the result will+-- be rendered after each iteration using @eventlog2html@.+profile :: FilePath -> Int -> Debuggee -> IO ()+profile outpath interval e = loop [(0, Map.empty)] 0+  where+    loop :: [(Int, CensusByClosureType)] -> Int -> IO ()+    loop ss i = do+      threadDelay (interval * 1_000_000)+      pause e+      r <- runTrace e $ do+        precacheBlocks+        rs <- gcRoots+        traceWrite (length rs)+        census2LevelClosureType rs+      resume e+      writeCensusByClosureType outpath r+      let new_data = ((i + 1) * interval, r) : ss+      renderProfile new_data+      loop new_data (i + 1)+++mkFrame :: (Int, CensusByClosureType) -> Frame+mkFrame (t, m) = Frame (fromIntegral t / 10e6) (Map.foldrWithKey (\k v r -> mkSample k v : r) [] m)++mkSample :: Text -> CensusStats -> Sample+mkSample k (CS _ (Size v) _) =+  Sample (Bucket k) (fromIntegral v)++mkProfData :: [(Int, CensusByClosureType)] -> ProfData+mkProfData raw_fs =+  let fs = map mkFrame raw_fs+      (counts, totals) = total fs+      -- Heap profiles don't contain any other information than the simple bucket name+      binfo = Map.mapWithKey (\(Bucket k) (t,s,g) -> BucketInfo k Nothing t s g) totals+  -- Heap profiles do not support traces+      header = Header "ghc-debug" "" (Just HeapProfBreakdownClosureType) "" "" "" counts Nothing+  in ProfData header binfo mempty fs [] (HeapInfo [] [] []) mempty++renderProfile :: [(Int, CensusByClosureType)] -> IO ()+renderProfile ss = do+  let pd = mkProfData ss+  as <- defaultArgs "unused"+  (header, data_json, descs, closure_descs) <- generateJsonData as pd+  let html = templateString header data_json descs closure_descs as+  writeFile "profile/ht.html" html+  return ()++
+ src/GHC/Debug/Profile/Types.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE DerivingVia #-}+module GHC.Debug.Profile.Types where++import           GHC.Debug.Types+import Data.Monoid+import Data.Semigroup++newtype Count = Count Int+                deriving (Semigroup, Monoid, Num) via Sum Int+                deriving (Show, Ord, Eq)++data CensusStats = CS { cscount :: !Count, cssize :: !Size, csmax :: !(Max Size) } deriving (Show, Eq)++mkCS :: Size -> CensusStats+mkCS i = CS (Count 1) i (Max i)++instance Monoid CensusStats where+  mempty = CS mempty mempty (Max (Size 0))++instance Semigroup CensusStats where+  (CS a b c) <> (CS a1 b1 c1) = CS (a <> a1) (b <> b1) (c <> c1)
+ src/GHC/Debug/Retainers.hs view
@@ -0,0 +1,77 @@+-- | Functions for computing retainers+module GHC.Debug.Retainers(findRetainersOf, findRetainers, addLocationToStack, displayRetainerStack) where++import GHC.Debug.Client+import Control.Monad.State+import GHC.Debug.Trace+import GHC.Debug.Types.Graph++import qualified Data.Set as Set+import Control.Monad.RWS++addOne :: [ClosurePtr] -> (Maybe Int, [[ClosurePtr]]) -> (Maybe Int, [[ClosurePtr]])+addOne _ (Just 0, cp) = (Just 0, cp)+addOne cp (n, cps)    = (subtract 1 <$> n, cp:cps)++findRetainersOf :: Maybe Int+                -> [ClosurePtr]+                -> [ClosurePtr]+                -> DebugM [[ClosurePtr]]+findRetainersOf limit cps bads = findRetainers limit cps (\cp _ -> return (cp `Set.member` bad_set))+  where+    bad_set = Set.fromList bads++-- | From the given roots, find any path to one of the given pointers.+-- Note: This function can be quite slow! The first argument is a limit to+-- how many paths to find. You should normally set this to a small number+-- such as 10.+findRetainers :: Maybe Int -> [ClosurePtr] -> (ClosurePtr -> SizedClosure -> DebugM Bool) -> DebugM [[ClosurePtr]]+findRetainers limit rroots p = (\(_, r, _) -> snd r) <$> runRWST (traceFromM funcs rroots) [] (limit, [])+  where+    funcs = TraceFunctions {+               papTrace = const (return ())+              , stackTrace = const (return ())+              , closTrace = closAccum+              , visitedVal = const (return ())+              , conDescTrace = const (return ())++            }+    -- Add clos+    closAccum  :: ClosurePtr+               -> SizedClosure+               -> RWST [ClosurePtr] () (Maybe Int, [[ClosurePtr]]) DebugM ()+               -> RWST [ClosurePtr] () (Maybe Int, [[ClosurePtr]]) DebugM ()+    closAccum cp sc k = do+      b <- lift $ p cp sc+      if b+        then do+          ctx <- ask+          modify' (addOne (cp: ctx))+          -- Don't call k, there might be more paths to the pointer but we+          -- probably just care about this first one.+        else do+          (lim, _) <- get+          case lim of+            Just 0 -> return ()+            _ -> local (cp:) k++addLocationToStack :: [ClosurePtr] -> DebugM [(SizedClosureC, Maybe SourceInformation)]+addLocationToStack r = do+  cs <- dereferenceClosures r+  cs' <- mapM (quadtraverse pure dereferenceConDesc pure pure) cs+  locs <- mapM getSourceLoc cs'+  return $ (zip cs' locs)+  where+    getSourceLoc c = getSourceInfo (tableId (info (noSize c)))++displayRetainerStack :: [(String, [(SizedClosureC, Maybe SourceInformation)])] -> IO ()+displayRetainerStack rs = do+      let disp (d, l) =+            (ppClosure ""  (\_ -> show) 0 . noSize $ d) ++  " <" ++ maybe "nl" tdisplay l ++ ">"+            where+              tdisplay sl = infoType sl ++ ":" ++ infoModule sl ++ ":" ++ infoPosition sl+          do_one k (l, stack) = do+            putStrLn (show k ++ "-------------------------------------")+            print l+            mapM (putStrLn . disp) stack+      zipWithM_ do_one [0 :: Int ..] rs
+ src/GHC/Debug/Snapshot.hs view
@@ -0,0 +1,46 @@+-- | Functions for creating and running snapshots.+module GHC.Debug.Snapshot ( -- * Generating snapshots+                            snapshot+                          , makeSnapshot+                          -- * Using a snapshot+                          , snapshotRun+                          -- * Low level+                          , traceFrom ) where++import GHC.Debug.Trace+import GHC.Debug.ParTrace+import GHC.Debug.Client.Monad+import GHC.Debug.Client+import Control.Monad.Identity+import Control.Monad.Trans++-- | Make a snapshot of the current heap and save it to the given file.+snapshot :: FilePath -> DebugM ()+snapshot fp = do+  precacheBlocks+  rs <- gcRoots+  _so <- savedObjects+  tracePar rs+  saveCache fp++-- | Traverse the tree from GC roots, to populate the caches+-- with everything necessary.+traceFrom :: [ClosurePtr] -> DebugM ()+traceFrom cps = runIdentityT (traceFromM funcs cps)+  where+    nop = const (return ())+    funcs = TraceFunctions nop nop clos (const (return ())) nop++    clos :: ClosurePtr -> SizedClosure -> (IdentityT DebugM) ()+              ->  (IdentityT DebugM) ()+    clos _cp sc k = do+      let itb = info (noSize sc)+      _traced <- lift $ getSourceInfo (tableId itb)+      k++-- | Pause the process and create a snapshot of+-- the heap. The snapshot can then be loaded with+-- 'snapshotRun' in order to perform offline analysis.+makeSnapshot :: Debuggee -> FilePath -> IO ()+makeSnapshot e fp = runAnalysis (snapshot fp) (const (return ())) e+
+ src/GHC/Debug/Trace.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE BangPatterns #-}+-- | Functions to support the constant space traversal of a heap.+module GHC.Debug.Trace ( traceFromM, TraceFunctions(..) ) where++import           GHC.Debug.Types+import GHC.Debug.Client.Monad+import           GHC.Debug.Client.Query++import qualified Data.IntMap as IM+import Data.Array.BitArray.IO+import Control.Monad.Reader+import Data.IORef+import Data.Word+import System.IO++newtype VisitedSet = VisitedSet (IM.IntMap (IOBitArray Word16))++data TraceState = TraceState { visited :: !VisitedSet, n :: !Int }+++getKeyPair :: ClosurePtr -> (Int, Word16)+getKeyPair cp =+  let BlockPtr raw_bk = applyBlockMask cp+      bk = fromIntegral raw_bk `div` 8+      offset = getBlockOffset cp `div` 8+  in (bk, fromIntegral offset)++checkVisit :: ClosurePtr -> IORef TraceState -> IO Bool+checkVisit cp mref = do+  st <- readIORef mref+  let VisitedSet v = visited st+      num_visited = n st+      (bk, offset) = getKeyPair cp+  case IM.lookup bk v of+    Nothing -> do+      na <- newArray (0, fromIntegral (blockMask `div` 8)) False+      writeArray na offset True+      writeIORef mref (TraceState (VisitedSet (IM.insert bk na v)) (num_visited + 1))+      when (num_visited `mod` 10_000 == 0) $ hPutStrLn stderr ("Traced: " ++ show num_visited)+      return False+    Just bm -> do+      res <- readArray bm offset+      unless res (writeArray bm offset True)+      return res++++data TraceFunctions m =+      TraceFunctions { papTrace :: !(GenPapPayload ClosurePtr -> m DebugM ())+      , stackTrace :: !(GenStackFrames ClosurePtr -> m DebugM ())+      , closTrace :: !(ClosurePtr -> SizedClosure -> m DebugM () -> m DebugM ())+      , visitedVal :: !(ClosurePtr -> (m DebugM) ())+      , conDescTrace :: !(ConstrDesc -> m DebugM ())+      }+++++type C m = (MonadTrans m, Monad (m DebugM))++-- | A generic heap traversal function which will use a small amount of+-- memory linear in the heap size. Using this function with appropiate+-- accumulation functions you should be able to traverse quite big heaps in+-- not a huge amount of memory.+traceFromM :: C m => TraceFunctions m-> [ClosurePtr] -> m DebugM ()+traceFromM k cps = do+  st <- lift (unsafeLiftIO (newIORef (TraceState (VisitedSet IM.empty) 1)))+  runReaderT (mapM_ (traceClosureFromM k) cps) st+{-# INLINE traceFromM #-}+{-# INLINE traceClosureFromM #-}++traceClosureFromM :: C m+                  => TraceFunctions m+                  -> ClosurePtr+                  -> ReaderT (IORef TraceState) (m DebugM) ()+traceClosureFromM !k = go+  where+    go cp = do+      mref <- ask+      b <- lift $ lift $ unsafeLiftIO (checkVisit cp mref)+      if b+        then lift $ visitedVal k cp+        else do+        sc <- lift $ lift $ dereferenceClosure cp+        ReaderT $ \st -> closTrace k cp sc+         (runReaderT (() <$ quadtraverse gop gocd gos go sc) st)+++    gos st = do+      st' <- lift $ lift $ dereferenceStack st+      lift $ stackTrace k st'+      () <$ traverse go st'++    gocd d = do+      cd <- lift $ lift $ dereferenceConDesc d+      lift $ conDescTrace k cd++    gop p = do+      p' <- lift $ lift $ dereferencePapPayload p+      lift $ papTrace k p'+      () <$ traverse go p'
+ src/GHC/Debug/TypePointsFrom.hs view
@@ -0,0 +1,287 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE ViewPatterns #-}+{- | Type Points From analysis in the style of+- Cork: Dynamic Memory Leak Detectionfor Garbage-Collected Languages+- https://dl.acm.org/doi/10.1145/1190216.1190224+- -}+module GHC.Debug.TypePointsFrom( typePointsFrom+                               , detectLeaks+                               , TypePointsFrom(..)+                               , getNodes+                               , getEdges+                               , edgeSource+                               , edgeTarget+                               , Key+                               , Edge(..)+                               , getKey+                               ) where++import GHC.Debug.Client.Monad+import GHC.Debug.Client+import Control.Monad.State+import GHC.Debug.ParTrace+import GHC.Debug.Types.Ptr+import qualified Data.Map.Monoidal.Strict as Map+import Data.Map (Map)+import qualified Data.Map.Internal as M+import GHC.Debug.Profile+import Control.Monad.Identity+import Control.Concurrent+import Data.List (sortOn)+import Language.Dot+import qualified Data.Set as S+++type Key = InfoTablePtr++data Edge = Edge !Key !Key deriving (Eq, Ord, Show)++edgeSource :: Edge -> Key+edgeTarget :: Edge -> Key+edgeSource (Edge k1 _) = k1+edgeTarget (Edge _ k2) = k2++data TypePointsFrom = TypePointsFrom { nodes :: !(Map.MonoidalMap Key CensusStats)+                                      , edges :: !(Map.MonoidalMap Edge CensusStats)+                                      } deriving (Show)++getNodes :: TypePointsFrom -> Map Key CensusStats+getEdges :: TypePointsFrom -> Map Edge CensusStats+getNodes = Map.getMonoidalMap . nodes+getEdges = Map.getMonoidalMap . edges++instance Monoid TypePointsFrom where+  mempty = TypePointsFrom mempty mempty++instance Semigroup TypePointsFrom where+  (TypePointsFrom a1 a2) <> (TypePointsFrom b1 b2) = TypePointsFrom (a1 <> b1) (a2 <> b2)++singletonTPF :: Key -> CensusStats -> [(Edge, CensusStats)] -> TypePointsFrom+singletonTPF k s es = TypePointsFrom (Map.singleton k s)+                                  (Map.fromList es)++-- | Perform a "type points from" heap census+typePointsFrom :: [ClosurePtr] -> DebugM TypePointsFrom+typePointsFrom cs = traceParFromM funcs (map (ClosurePtrWithInfo Root) cs)++  where+    nop = const (return ())+    funcs = TraceFunctionsIO nop nop clos visit nop++    visit :: ClosurePtr -> Context -> DebugM TypePointsFrom+    visit cp ctx = do+      sc <- dereferenceClosure cp+      let k = tableId $ info (noSize sc)+          v = mkCS (dcSize sc)+          parent_edge = case ctx of+                          Root -> []+                          Parent pk -> [(Edge k pk, v)]+      return $ TypePointsFrom Map.empty (Map.fromList parent_edge)++++    clos :: ClosurePtr -> SizedClosure -> Context+              -> DebugM (Context, TypePointsFrom, DebugM () -> DebugM ())+    clos _cp sc ctx = do+      let k = tableId $ info (noSize sc)+      let s :: Size+          s = dcSize sc+          v =  mkCS s++          -- Edges point from the object TO what retains it+          parent_edge = case ctx of+                          Root -> []+                          Parent pk -> [(Edge k pk, v)]++      return (Parent k, singletonTPF k v parent_edge, id)+++data Context = Root | Parent Key+++-- | Repeatedly call 'typesPointsFrom' and perform the leak detection+-- analysis.+detectLeaks :: Int -> Debuggee -> IO ()+detectLeaks interval e = loop Nothing (M.empty, M.empty) 0+  where+    loop :: Maybe TypePointsFrom -> RankMaps -> Int -> IO ()+    loop prev_census rms i = do+      print i+      threadDelay (interval * 1_000_000)+      pause e+      (gs, r, new_rmaps) <- runTrace e $ do+        _ <- precacheBlocks+        rs <- gcRoots+        traceWrite (length rs)+        res <- typePointsFrom rs+        let !new_rmaps = case prev_census of+                           Nothing -> rms+                           Just pcensus -> updateRankMap rms pcensus res+        let cands = chooseCandidates (fst new_rmaps)+        traceWrite (length cands)+        gs <- mapM (findSlice (snd new_rmaps)) (take 10 cands)+        return (gs, res, new_rmaps)+      resume e+      zipWithM_ (\n g -> writeFile ("slices/"+                                      ++ show @Int i ++ "-"+                                      ++ show @Int n ++ ".dot")+                                   (renderDot g)) [0..] gs+      loop (Just r) new_rmaps (i + 1)+++-- Analysis code+--+getKey :: InfoTablePtr -> DebugM String+getKey itblp = do+    loc <- getSourceInfo itblp+    itbl <- dereferenceInfoTable itblp+    case loc of+      Nothing -> getKeyFallback itblp itbl+      Just s -> return $ show (tipe itbl) ++ ":" ++ renderSourceInfo s++getKeyFallback itbp itbl = do+    case tipe itbl of+      t | CONSTR <= t && t <= CONSTR_NOCAF   -> do+        ConstrDesc a b c <- dereferenceConDesc itbp+        return $ a ++ ":" ++ b ++ ":" ++ c+      _ -> return $ show (tipe itbl)++type Rank = Double+type Decay = Double++data RankInfo = RankInfo !Rank !Int deriving Show++getRank :: RankInfo -> Rank+getRank (RankInfo r _) = r++default_decay :: Decay+default_decay = 0.15++rank_threshold :: Double+rank_threshold = 100++min_iterations :: Int+min_iterations = 2++applyRankFilter :: RankInfo -> Bool+applyRankFilter (RankInfo r i) = r >= rank_threshold && i >= min_iterations++-- | Lookup suitable candidates from the RankMap+-- , Chooses values based on 'rank_threshold' and 'min_iterations'+lookupRM :: Key -> RankMap Edge -> [(Edge, RankInfo)]+lookupRM k m = M.assocs filtered_map+  where+    -- TODO, work out how to use these functions O(log n)+    --smaller =  traceShow (M.size m) (M.dropWhileAntitone ((/= k) . edgeSource) $ m)+    --res_map = traceShow (M.size smaller) (M.takeWhileAntitone ((== k) . edgeSource) smaller)+    (res_map, _) = M.partitionWithKey (\e _ -> (== k) . edgeSource $ e) m+    filtered_map = M.filter (\(RankInfo r _) -> r > 0) res_map++mkDotId :: InfoTablePtr -> Id+mkDotId (InfoTablePtr w) = IntegerId (fromIntegral w)++findSlice :: RankMap Edge -> Key -> DebugM Graph+findSlice rm k = Graph StrictGraph DirectedGraph (Just (mkDotId k)) <$> evalStateT (go 3 k) S.empty++  where++    go :: Int -> InfoTablePtr -> StateT (S.Set InfoTablePtr) DebugM [Statement]+    go n cur_k = do+      visited_set <- get+      -- But don't stop going deep until we've seen a decent number of+      -- nodes+      if S.member cur_k visited_set || (n <= 0 && S.size visited_set >= 20)+        then return []+        else do+          label <- lift $ getKey cur_k+          let next_edges = take 20 (lookupRM cur_k rm)+              -- Decoding very wide is bad+              edge_stmts = map mkEdge next_edges+              node_stmt = NodeStatement (NodeId (mkDotId cur_k) Nothing) [AttributeSetValue (StringId "label") (StringId label) ]+              mkEdge (Edge _ e, ri) = EdgeStatement [ENodeId NoEdge (NodeId (mkDotId cur_k) Nothing), ENodeId DirectedEdge (NodeId (mkDotId e) Nothing)] [AttributeSetValue (StringId "label") (StringId (show (getRank ri))) ]++          modify' (S.insert cur_k)+          ss <- concat <$> mapM (go (n-1) . edgeTarget . fst) next_edges+          return $ node_stmt : edge_stmts ++ ss++renderSourceInfo :: SourceInformation -> String+renderSourceInfo s = escapeQuotes (infoName s ++ ":" ++ infoType s ++ ":" ++ infoPosition s)++escapeQuotes :: String -> String+escapeQuotes [] = []+escapeQuotes ('"':xs) = '\\' : '"' : escapeQuotes xs+escapeQuotes (x:xs) = x:escapeQuotes xs+++chooseCandidates :: RankMap Key -> [Key]+chooseCandidates = map fst . reverse . sortOn (getRank . snd) . M.assocs . M.filter applyRankFilter++type RankMap k = M.Map k RankInfo++type RankMaps = (RankMap Key, RankMap Edge)++type RankUpdateMap k = M.Map k RankUpdateInfo++type RankUpdateInfo = Int -> Double -> Double++-- | Update the current rank predictions based on the difference between+-- two censuses.+updateRankMap :: (RankMap Key, RankMap Edge)+              -> TypePointsFrom+              -> TypePointsFrom+              -> (RankMap Key, RankMap Edge)+updateRankMap (rm_n, rm_e) t1 t2 = (ns, es)+  where+    !(rnodes, redges) = ratioRank t1 t2+    missingL = M.dropMissing+    missingR = M.mapMissing (\_ f -> RankInfo (f 0 0) 1)+    matched = M.zipWithMatched (\_ (RankInfo r iters) f -> RankInfo (f iters r) (iters + 1))++    !ns = runIdentity $ M.mergeA missingL missingR matched rm_n rnodes+    !es = runIdentity $ M.mergeA missingL missingR matched rm_e redges+++compareSize :: CensusStats -> CensusStats -> Maybe (Int -> Double -> Double)+compareSize (cssize -> Size s1) (cssize -> Size s2) =+  if fromIntegral s2 > (1 - default_decay) * fromIntegral s1+    -- Calculate "Q"+    then if s1 > s2+          -- Shrinking phase, penalise rank+          then Just (\phases rank ->+                      rank+                        - ((fromIntegral (phases + 1))+                            * ((fromIntegral s1 / fromIntegral s2) - 1)))+          else Just (\phases rank ->+                        rank ++                          ((fromIntegral (phases + 1))+                            * ((fromIntegral s2 / fromIntegral s1) - 1)))+    else Nothing++-- | Compute how to update the ranks based on the difference between two+-- censuses.+ratioRank :: TypePointsFrom -> TypePointsFrom -> (RankUpdateMap Key, RankUpdateMap Edge)+ratioRank t1 t2 = (candidates, redges)+  where+    ns1 = getNodes t1+    ns2 = getNodes t2++    es1 = getEdges t1+    es2 = getEdges t2+    missingL = M.dropMissing+    missingR = M.dropMissing+    matched = M.zipWithMaybeMatched (\_ cs1 cs2 -> compareSize cs1 cs2)+    !candidates = runIdentity $ M.mergeA missingL missingR matched ns1 ns2++    !redges = runIdentity $ M.mergeA missingL missingR matched es1 es2+++