summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/GHC/Driver/Flags.hs1
-rw-r--r--compiler/GHC/Driver/Make.hs293
-rw-r--r--compiler/GHC/Driver/Session.hs2
-rw-r--r--compiler/GHC/Utils/Error.hs96
-rw-r--r--docs/users_guide/debugging.rst22
5 files changed, 362 insertions, 52 deletions
diff --git a/compiler/GHC/Driver/Flags.hs b/compiler/GHC/Driver/Flags.hs
index b1154b6398..9f1317e46c 100644
--- a/compiler/GHC/Driver/Flags.hs
+++ b/compiler/GHC/Driver/Flags.hs
@@ -135,6 +135,7 @@ data DumpFlag
| Opt_D_dump_mod_cycles
| Opt_D_dump_mod_map
| Opt_D_dump_timings
+ | Opt_D_dump_make_stats
| Opt_D_dump_view_pattern_commoning
| Opt_D_verbose_core2core
| Opt_D_dump_debug
diff --git a/compiler/GHC/Driver/Make.hs b/compiler/GHC/Driver/Make.hs
index 53d8040c9c..622c8fa7b4 100644
--- a/compiler/GHC/Driver/Make.hs
+++ b/compiler/GHC/Driver/Make.hs
@@ -16,6 +16,8 @@
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE TypeApplications #-}
-- -----------------------------------------------------------------------------
--
@@ -152,6 +154,13 @@ import GHC.Utils.Constants
import GHC.Types.Unique.DFM (udfmRestrictKeysSet)
import qualified Data.IntSet as I
import GHC.Types.Unique
+import Debug.Trace
+import GHC.Utils.Json
+import Data.Functor.Identity
+import Data.Ord
+import Data.List (sortBy)
+import qualified Data.Set as S
+import Text.Printf
-- -----------------------------------------------------------------------------
@@ -977,15 +986,25 @@ waitResult :: ResultVar a -> MaybeT IO a
waitResult (ResultVar f var) = MaybeT (fmap f <$> readMVar var)
data BuildResult = BuildResult { _resultOrigin :: ResultOrigin
+ , resultMakeId :: MakeActionId -- ^ The corresponding Make action which are going to fill in
, resultVar :: ResultVar (Maybe HomeModInfo, ModuleNameSet)
}
-- The origin of this result var, useful for debugging
-data ResultOrigin = NoLoop | Loop ResultLoopOrigin deriving (Show)
+data ResultOrigin = NoLoop | Loop ResultLoopOrigin
-data ResultLoopOrigin = Initialise | Rehydrated | Finalised deriving (Show)
+data ResultLoopOrigin = Initialise | Rehydrated | Finalised
-mkBuildResult :: ResultOrigin -> ResultVar (Maybe HomeModInfo, ModuleNameSet) -> BuildResult
+instance Outputable ResultLoopOrigin where
+ ppr Initialise = text "Initialise"
+ ppr Rehydrated = text "Rehydrated"
+ ppr Finalised = text "Finalised"
+
+instance Outputable ResultOrigin where
+ ppr (NoLoop) = text "NL"
+ ppr (Loop ro) = text "L(" <> ppr ro <> text ")"
+
+mkBuildResult :: ResultOrigin -> MakeActionId -> ResultVar (Maybe HomeModInfo, ModuleNameSet) -> BuildResult
mkBuildResult = BuildResult
@@ -994,6 +1013,7 @@ data BuildLoopState = BuildLoopState { buildDep :: M.Map NodeKey BuildResult
-- the appropriate result of compiling a module but with
-- cycles there can be additional indirection and can point to the result of typechecking a loop
, nNODE :: Int
+ , nMAKE :: Int
, hug_var :: MVar HomeUnitGraph
-- A global variable which is incrementally updated with the result
-- of compiling modules.
@@ -1005,6 +1025,12 @@ nodeId = do
modify (\m -> m { nNODE = n + 1 })
return n
+makeId :: BuildM MakeActionId
+makeId = do
+ n <- gets nMAKE
+ modify (\m -> m { nMAKE = n + 1 })
+ return (MakeActionId n)
+
setModulePipeline :: NodeKey -> BuildResult -> BuildM ()
setModulePipeline mgn build_result = do
@@ -1027,8 +1053,8 @@ type BuildM a = StateT BuildLoopState IO a
data AbstractSem = AbstractSem { acquireSem :: IO ()
, releaseSem :: IO () }
-withAbstractSem :: AbstractSem -> IO b -> IO b
-withAbstractSem sem = MC.bracket_ (acquireSem sem) (releaseSem sem)
+withAbstractSem :: (MonadIO m, MC.MonadMask m) => AbstractSem -> m b -> m b
+withAbstractSem sem = MC.bracket_ (liftIO $ acquireSem sem) (liftIO $ releaseSem sem)
-- | Environment used when compiling a module
data MakeEnv = MakeEnv { hsc_env :: !HscEnv -- The basic HscEnv which will be augmented for each module
@@ -1056,7 +1082,7 @@ interpretBuildPlan :: HomeUnitGraph
, IO [Maybe (Maybe HomeModInfo)]) -- An action to query to get all the built modules at the end.
interpretBuildPlan hug mhmi_cache old_hpt plan = do
hug_var <- newMVar hug
- ((mcycle, plans), build_map) <- runStateT (buildLoop plan) (BuildLoopState M.empty 1 hug_var)
+ ((mcycle, plans), build_map) <- runStateT (buildLoop plan) (BuildLoopState M.empty 1 0 hug_var)
let wait = collect_results (buildDep build_map)
return (mcycle, plans, wait)
@@ -1106,9 +1132,8 @@ interpretBuildPlan hug mhmi_cache old_hpt plan = do
-- which would retain all the result variables, preventing us from collecting them
-- after they are no longer used.
!build_deps = getDependencies direct_deps build_map
- let build_action =
+ let build_action (hug, deps) =
withCurrentUnit (moduleGraphNodeUnitId mod) $ do
- (hug, deps) <- wait_deps_hug hug_var build_deps
case mod of
InstantiationNode uid iu -> do
executeInstantiationNode mod_idx n_mods hug uid iu
@@ -1131,8 +1156,9 @@ interpretBuildPlan hug mhmi_cache old_hpt plan = do
res_var <- liftIO newEmptyMVar
let result_var = mkResultVar res_var
- setModulePipeline (mkNodeKey mod) (mkBuildResult origin result_var)
- return $ (MakeAction build_action res_var)
+ make_action <- makeAction (MakeModule (mkNodeKey mod)) build_deps (wait_deps_hug hug_var) build_action res_var
+ setModulePipeline (mkNodeKey mod) (mkBuildResult origin (make_action_id make_action) result_var)
+ return make_action
buildOneLoopyModule :: ModuleGraphNodeWithBootFile -> BuildM [MakeAction]
@@ -1163,8 +1189,7 @@ interpretBuildPlan hug mhmi_cache old_hpt plan = do
res_var <- liftIO newEmptyMVar
let
!build_deps = getDependencies (map gwib_mod deps) build_map
- let loop_action = do
- (hug, tdeps) <- wait_deps_hug hug_var build_deps
+ let loop_action (hug, tdeps) = do
hsc_env <- asks hsc_env
let new_hsc = setHUG hug hsc_env
mns :: [ModuleName]
@@ -1178,6 +1203,8 @@ interpretBuildPlan hug mhmi_cache old_hpt plan = do
liftIO $ modifyMVar_ hug_var (\hug -> return $ foldr addHomeModInfoToHug hug hmis')
return (hmis', tdeps)
+ action <- makeAction LoopSync build_deps (wait_deps_hug hug_var) loop_action res_var
+
let fanout i = first (Just . (!! i)) <$> mkResultVar res_var
-- From outside the module loop, anyone must wait for the loop to finish and then
-- use the result of the rehydrated iface. This makes sure that things not in the
@@ -1186,18 +1213,21 @@ interpretBuildPlan hug mhmi_cache old_hpt plan = do
boot_key (NodeKey_Module m) = NodeKey_Module (m { mnkModuleName = (mnkModuleName m) { gwib_isBoot = IsBoot } } )
boot_key k = pprPanic "boot_key" (ppr k)
+ make_id = make_action_id action
+
update_module_pipeline (m, i) =
case gwib_isBoot m of
- NotBoot -> setModulePipeline (gwib_mod m) (mkBuildResult (Loop origin) (fanout i))
+ NotBoot -> setModulePipeline (gwib_mod m) (mkBuildResult (Loop origin) make_id (fanout i))
IsBoot -> do
- setModulePipeline (gwib_mod m) (mkBuildResult (Loop origin) (fanout i))
+ setModulePipeline (gwib_mod m) (mkBuildResult (Loop origin) make_id (fanout i))
-- SPECIAL: Anything outside the loop needs to see A rather than A.hs-boot
- setModulePipeline (boot_key (gwib_mod m)) (mkBuildResult (Loop origin) (fanout i))
+ setModulePipeline (boot_key (gwib_mod m)) (mkBuildResult (Loop origin) make_id (fanout i))
let deps_i = zip deps [0..]
mapM update_module_pipeline deps_i
- return $ MakeAction loop_action res_var
+ return action
+
-- Checks that the interfaces returned from hydration match-up with the names of the
-- modules which were fed into the function.
@@ -1222,7 +1252,13 @@ upsweep
upsweep n_jobs hsc_env hmi_cache mHscMessage old_hpt build_plan = do
(cycle, pipelines, collect_result) <- interpretBuildPlan (hsc_HUG hsc_env) hmi_cache old_hpt build_plan
runPipelines n_jobs hsc_env mHscMessage pipelines
- res <- collect_result
+ res <-
+ if dopt Opt_D_dump_make_stats (hsc_dflags hsc_env)
+ then do
+ let !meta = strictMap make_action_meta pipelines
+ collect_result <* analyseBuildGraph (hsc_logger hsc_env) meta
+ else
+ collect_result
let completed = [m | Just (Just m) <- res]
let hsc_env' = addDepsToHscEnv completed hsc_env
@@ -2475,7 +2511,7 @@ executeCompileNode :: Int
executeCompileNode k n !old_hmi hug mrehydrate_mods mod = do
me@MakeEnv{..} <- ask
-- Rehydrate any dependencies if this module had a boot file or is a signature file.
- lift $ MaybeT (withAbstractSem compile_sem $ withLoggerHsc k me $ \hsc_env -> do
+ lift $ MaybeT (withLoggerHsc k me $ \hsc_env -> do
hydrated_hsc_env <- liftIO $ maybeRehydrateBefore (setHUG hug hsc_env) mod fixed_mrehydrate_mods
let -- Use the cached DynFlags which includes OPTIONS_GHC pragmas
lcl_dynflags = ms_hspp_opts mod
@@ -2725,7 +2761,7 @@ executeLinkNode hug kn uid deps = do
let hsc_env' = setHUG hug hsc_env
msg' = (\messager -> \recomp -> messager hsc_env kn recomp (LinkNode deps uid)) <$> env_messager
- linkresult <- liftIO $ withAbstractSem compile_sem $ do
+ linkresult <- liftIO $
link (ghcLink dflags)
(hsc_logger hsc_env')
(hsc_tmpfs hsc_env')
@@ -2789,7 +2825,7 @@ wait_deps_hug hug_var deps = do
let -- Restrict to things which are in the transitive closure to avoid retaining
-- reference to loop modules which have already been compiled by other threads.
-- See Note [ModuleNameSet, efficiency and space leaks]
- !new = udfmRestrictKeysSet (homeUnitEnv_hpt hme) (fromMaybe I.empty $ M.lookup uid module_deps)
+ !new = udfmRestrictKeysSet (homeUnitEnv_hpt hme) (fromMaybe I.empty $ M.lookup uid module_deps)
in hme { homeUnitEnv_hpt = new }
return (unitEnv_mapWithKey pruneHomeUnitEnv hug, module_deps)
@@ -2919,22 +2955,225 @@ runAllPipelines n_jobs env acts = do
-- semaphore.
runLoop :: (((forall a. IO a -> IO a) -> IO ()) -> IO a) -> MakeEnv -> [MakeAction] -> IO [a]
runLoop _ _env [] = return []
-runLoop fork_thread env (MakeAction act res_var :acts) = do
- new_thread <-
+runLoop fork_thread env (ma:acts) = do
+ new_thread <- forkMakeAction fork_thread env ma
+ threads <- runLoop fork_thread env acts
+ return (new_thread : threads)
+
+
+forkMakeAction :: (((forall a. IO a -> IO a) -> IO ()) -> IO a) -> MakeEnv -> MakeAction -> IO a
+forkMakeAction fork_thread env (MakeAction _deps wait act res_var (MakeActionMeta action_name id _ timing_var)) =
fork_thread $ \unmask -> (do
- mres <- (unmask $ run_pipeline (withLocalTmpFS act))
+ mres <- (unmask $ run_pipeline (withLocalTmpFS $ do
+ wait >>= withAbstractSem (compile_sem env)
+ . with_timing . act
+
+ ))
`MC.onException` (putMVar res_var Nothing) -- Defensive: If there's an unhandled exception then still signal the failure.
putMVar res_var mres)
- threads <- runLoop fork_thread env acts
- return (new_thread : threads)
where
run_pipeline :: RunMakeM a -> IO (Maybe a)
run_pipeline p = runMaybeT (runReaderT p env)
-data MakeAction = forall a . MakeAction (RunMakeM a) (MVar (Maybe a))
+ with_timing = withTimingSilentX (hsc_logger $ hsc_env env)
+ Opt_D_dump_make_stats
+ action_herald
+ (const ())
+ write_timing_result
+
+ action_herald = text "MAKE:" <> ppr id <+> ppr action_name
+ write_timing_result = liftIO . writeIORef (getIORefMaybe timing_var) . Just
+
+data MakeActionOrigin = MakeModule NodeKey | LoopSync
+
+instance Outputable MakeActionOrigin where
+ ppr (MakeModule nk) = text "M:" <+> ppr nk
+ ppr LoopSync = text "LoopSync"
+
+newtype MakeActionId = MakeActionId { getMakeActionId :: Int } deriving (Eq, Ord, Show)
+
+instance Outputable MakeActionId where
+ ppr (MakeActionId n) = ppr n
+
+data MakeAction = forall a b . MakeAction { make_deps :: [BuildResult] -- Dependencies of this action
+ , make_wait :: RunMakeM b -- The action to run to get the result of these deps
+ , make_action :: (b -> RunMakeM a) -- How to build the action once the depenencies are ready
+ , make_res_var :: (MVar (Maybe a)) -- Where to put the result of running the action
+ , make_action_meta :: MakeActionMeta -- Meta information about the action
+ }
+
+makeAction :: MakeActionOrigin -> [BuildResult] -> ([BuildResult] -> RunMakeM b) -> (b -> RunMakeM a) -> (MVar (Maybe a)) -> BuildM MakeAction
+makeAction make_action_meta_origin make_deps make_wait_deps make_action make_res_var = do
+ make_action_meta_id <- makeId
+ make_action_meta_timing <- IORefMaybe <$> liftIO (newIORef Nothing)
+ let make_wait = make_wait_deps make_deps
+ !make_action_meta_dep_ids = strictMap resultMakeId make_deps
+ make_action_meta = MakeActionMeta{..}
+ ma = MakeAction{..}
+ reportNode ma
+ return ma
+
+
+-- | Record a new edge from the build graph
+reportNode :: MakeAction -> BuildM ()
+reportNode ma = do
+ -- TODO: here we emit to eventlog and also store in memory?
+ let mk_int = JSInt . getMakeActionId
+ liftIO $ traceEventIO
+ (showSDocUnsafe $ text "node:" <> renderJSON (JSObject [("node_id", mk_int (make_action_id ma))
+ , ("node_deps", JSArray (map (mk_int . resultMakeId) (make_deps ma)))
+ , ("node_desc", JSString (showSDocUnsafe (ppr (make_action_origin ma))))
+ ]))
waitMakeAction :: MakeAction -> IO ()
-waitMakeAction (MakeAction _ mvar) = () <$ readMVar mvar
+waitMakeAction (MakeAction{make_res_var}) = () <$ readMVar make_res_var
+
+type MakeActionMeta = MakeActionMetaX IORefMaybe
+
+-- | Separate data type as want to avoid retaining MVars pointing to the results.
+data MakeActionMetaX f = MakeActionMeta { make_action_meta_origin :: !MakeActionOrigin -- Where the action originated from
+ , make_action_meta_dep_ids :: ![MakeActionId] -- Ids of the dependencies
+ , make_action_meta_id :: !MakeActionId -- Id of the current action
+ , make_action_meta_timing :: !(f TimingInfo) -- Information about how long the action took
+ }
+
+instance Outputable (f TimingInfo) => Outputable (MakeActionMetaX f) where
+ ppr (MakeActionMeta o deps id timing) =
+ text "ActionMeta:" <+> vcat [ text "id:" <+> ppr id
+ , text "deps:" <+> ppr deps
+ , text "origin:" <+> ppr o
+ , text "timing:" <+> ppr timing ]
+
+
+traverseMakeActionMetaX :: Monad m => (forall a . f a -> m (g a)) -> MakeActionMetaX f -> m (MakeActionMetaX g)
+traverseMakeActionMetaX nat (MakeActionMeta{..}) =
+ nat make_action_meta_timing >>= \new -> return $ MakeActionMeta{make_action_meta_timing = new, ..}
+
+newtype IORefMaybe a = IORefMaybe { getIORefMaybe :: IORef (Maybe a)}
+
+make_action_id :: MakeAction -> MakeActionId
+make_action_id = make_action_meta_id . make_action_meta
+make_action_origin :: MakeAction -> MakeActionOrigin
+make_action_origin = make_action_meta_origin . make_action_meta
+
+-- Analysis Scripts
+
+analyseBuildGraph :: Logger -> [MakeActionMeta] -> IO ()
+analyseBuildGraph logger metas_io = do
+ new_metas <- mapM (traverseMakeActionMetaX (readIORef . getIORefMaybe)) metas_io
+ let all_completed = all (isJust . make_action_meta_timing) new_metas
+ when all_completed $ do
+ let Identity new_metas_completed = mapM (traverseMakeActionMetaX (return . Identity . fromJust)) new_metas
+ let -- Longest path to each node
+ lp = longest_path (info_map new_metas_completed)
+ earliest_complete = earliest_finish_time new_metas_completed lp
+ -- Earliest we could possibly finish with infinite processors
+ latest_finish = maximum earliest_complete
+ -- Total time if we did -j1
+ seq_time = sum (map (timingMillisecs . runIdentity . make_action_meta_timing) new_metas_completed)
+ parrelism_score =
+ seq_time
+ / latest_finish
+
+ im = info_map new_metas_completed
+
+
+ max_flows = sortBy (comparing snd) $ M.assocs (M.map fst lp)
+
+ timing_for_id mid = timingMillisecs $ runIdentity $ make_action_meta_timing (im M.! mid)
+
+ flow_x_time :: MakeActionId -> (Flow, b) -> Double
+ flow_x_time mid (flow, _) = realToFrac (getFlow flow) * timing_for_id mid
+
+ max_flows_x_time = sortBy (comparing snd) $ M.assocs (M.mapWithKey flow_x_time lp)
+
+ max_dur = sortBy (comparing (fmap timingMillisecs . make_action_meta_timing)) new_metas_completed
+
+ -- Printing logic
+ let print_id_pair :: (a -> SDoc) -> (MakeActionId, a) -> SDoc
+ print_id_pair ppr_a (mid, dat) = ppr (make_action_meta_origin info) <+> parens (int (getMakeActionId mid)) <> colon <+> ppr_a dat
+ where
+ info = im M.! mid
+
+
+ header s = text "=====" <+> text s <+> text "====="
+ block s body = vcat [ header s, body ]
+ vcat_numbered docs = vcat $ zipWith (\n doc -> text (printf "%0*d" padding n) <+> doc) [0 :: Int ..] docs
+ where
+ padding = ceiling @Double @Int (logBase 10 (fromIntegral $ length docs))
+
+ logDumpFile logger (mkDumpStyle alwaysQualify) Opt_D_dump_make_stats "make" FormatText $ vcat [
+ block "Maximum Duration" (
+ vcat_numbered (map (print_id_pair (ppr . runIdentity)) (map ((,) <$> make_action_meta_id <*> make_action_meta_timing) (reverse $ max_dur)))
+ ),
+ block "Maximum Flows"
+ (vcat_numbered (map (print_id_pair ppr) (reverse max_flows))),
+ block "Flows x Time"
+ (vcat_numbered (map (print_id_pair (doublePrec 3)) (reverse max_flows_x_time))),
+ block "Statistics"
+ (vcat [ text "longest path" <> colon <+> doublePrec 3 latest_finish <> text "s"
+ , text "parallelism score" <> colon <+> doublePrec 3 parrelism_score
+ , text "sequential time" <> colon <+> doublePrec 3 seq_time <> text "s"
+ ]) ]
+
+
+
+ where
+
+ MakeActionId last_action_id = make_action_meta_id $ last (sortBy (comparing make_action_meta_id) metas_io)
+
+ first_action_ids = map make_action_meta_id $ (filter (null . make_action_meta_dep_ids) metas_io)
+
+ info_map :: [MakeActionMetaX f] -> M.Map MakeActionId (MakeActionMetaX f)
+ info_map metas = M.fromList [(make_action_meta_id m, m) | m <- metas]
+
+ earliest_finish_time :: [MakeActionMetaX Identity] -> M.Map MakeActionId (Flow, Double) -> M.Map MakeActionId Double
+ earliest_finish_time meta_info m = Map.fromList
+ [(make_action_meta_id, timingMillisecs t + (maybe 0 snd $ M.lookup make_action_meta_id m))
+ | MakeActionMeta{..} <- meta_info
+ , Identity t <- [make_action_meta_timing] ]
+
+ -- Creates a map of "earliest start time"
+ longest_path :: M.Map MakeActionId (MakeActionMetaX Identity) -> M.Map MakeActionId (Flow, Double)
+ longest_path node_info = foldl' go M.empty (map MakeActionId [0..last_action_id])
+ where
+ reverse_deps :: M.Map MakeActionId (S.Set MakeActionId)
+ reverse_deps = Map.fromListWith (S.union) [(dep, S.singleton make_action_meta_id) | MakeActionMeta{..} <- metas_io, dep <- make_action_meta_dep_ids]
+
+ go :: M.Map MakeActionId (Flow, Double)
+ -> MakeActionId
+ -> M.Map MakeActionId (Flow, Double)
+ go m cur_id =
+ let
+ (flow_to_me, time_to_me, m') = case M.lookup cur_id m of
+ Just (flow, time) -> (flow, time, m)
+ Nothing -> (initial_flow, 0, M.insert cur_id (initial_flow, 0) m)
+
+ cur_info = node_info M.! cur_id
+ rev_deps = fromMaybe S.empty $ M.lookup cur_id reverse_deps
+ Identity cur_time = make_action_meta_timing cur_info
+ out_flow = splitFlow flow_to_me (length rev_deps)
+ in foldl' (update_children out_flow (time_to_me + timingMillisecs cur_time)) m' rev_deps
+
+ update_children new_f new_t m upd_id =
+ M.insertWith comb upd_id (new_f, new_t) m
+
+ comb (a1, b1) (a2, b2) = (a1 `addFlow` a2, max b1 b2)
+
+ initial_flow = splitFlow initialFlow (length first_action_ids)
+
+newtype Flow = Flow { getFlow :: Rational } deriving (Eq, Show, Ord)
+
+instance Outputable Flow where
+ ppr (Flow f) = doublePrec 3 (realToFrac f)
+
+initialFlow :: Flow
+initialFlow = Flow 1
+splitFlow :: Flow -> Int -> Flow
+splitFlow (Flow f) n = Flow (f / fromIntegral n)
+addFlow :: Flow -> Flow -> Flow
+addFlow (Flow f) (Flow g) = Flow (f + g)
+
{- Note [GHC Heap Invariants]
~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/compiler/GHC/Driver/Session.hs b/compiler/GHC/Driver/Session.hs
index 168a204fbc..84a6ed607f 100644
--- a/compiler/GHC/Driver/Session.hs
+++ b/compiler/GHC/Driver/Session.hs
@@ -2582,6 +2582,8 @@ dynamic_flags_deps = [
(setDumpFlag Opt_D_dump_mod_map)
, make_ord_flag defGhcFlag "ddump-timings"
(setDumpFlag Opt_D_dump_timings)
+ , make_ord_flag defGhcFlag "ddump-make-stats"
+ (setDumpFlag Opt_D_dump_make_stats)
, make_ord_flag defGhcFlag "ddump-view-pattern-commoning"
(setDumpFlag Opt_D_dump_view_pattern_commoning)
, make_ord_flag defGhcFlag "ddump-to-file"
diff --git a/compiler/GHC/Utils/Error.hs b/compiler/GHC/Utils/Error.hs
index 8910dd4d38..a24120123d 100644
--- a/compiler/GHC/Utils/Error.hs
+++ b/compiler/GHC/Utils/Error.hs
@@ -3,6 +3,7 @@
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE RecordWildCards #-}
{-
(c) The AQUA Project, Glasgow University, 1994-1998
@@ -54,7 +55,8 @@ module GHC.Utils.Error (
fatalErrorMsg,
compilationProgressMsg,
showPass,
- withTiming, withTimingSilent,
+ TimingInfo(..), timingMillisecs,
+ withTiming, withTimingSilent, withTimingSilentX,
debugTraceMsg,
ghcExit,
prettyPrintGhcErrors,
@@ -88,7 +90,8 @@ import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Catch as MC (handle)
import GHC.Conc ( getAllocationCounter )
-import System.CPUTime
+import Data.Int (Int64)
+import Data.Time (getCurrentTime, UTCTime, diffUTCTime)
data DiagOpts = DiagOpts
{ diag_warning_flags :: !(EnumSet WarningFlag) -- ^ Enabled warnings
@@ -305,6 +308,20 @@ showPass logger what =
data PrintTimings = PrintTimings | DontPrintTimings
deriving (Eq, Show)
+-- | The information collected by withTimings
+data TimingInfo = TimingInfo { timingPhase :: SDoc
+ , timingStart, timingEnd :: UTCTime -- ^ The time in picoseconds
+ , timingAllocs :: Int64}
+
+instance Outputable TimingInfo where
+ ppr t =
+ text "time:" <+> doublePrec 2 (timingMillisecs t)
+ <+> text "allocs:" <+> doublePrec 2 (realToFrac (timingAllocs t) / 1024 / 1024)
+
+
+timingMillisecs :: TimingInfo -> Double
+timingMillisecs TimingInfo{..} = realToFrac (timingEnd `diffUTCTime` timingStart) -- / 1000
+
-- | Time a compilation phase.
--
-- When timings are enabled (e.g. with the @-v2@ flag), the allocations
@@ -337,7 +354,7 @@ withTiming :: MonadIO m
-> m a -- ^ The body of the phase to be timed
-> m a
withTiming logger what force action =
- withTiming' logger what force PrintTimings action
+ withTiming' logger Opt_D_dump_timings what force PrintTimings defaultTimingOutput action
-- | Same as 'withTiming', but doesn't print timings in the
-- console (when given @-vN@, @N >= 2@ or @-ddump-timings@).
@@ -352,54 +369,83 @@ withTimingSilent
-> m a -- ^ The body of the phase to be timed
-> m a
withTimingSilent logger what force action =
- withTiming' logger what force DontPrintTimings action
+ withTiming' logger Opt_D_dump_timings what force DontPrintTimings defaultTimingOutput action
+
+ -- | Same as 'withTimingSilent' but allows you to pass the continuation about how
+ -- to deal with the timing info at the end. This is used by the --make driver to
+ -- record how long it took to compile a module so we can do some analysis on the timings
+ -- after the build has completed.
+withTimingSilentX :: MonadIO m
+ => Logger -> DumpFlag -> SDoc -> (a -> ())
+ -> (TimingInfo -> m ()) -> m a -> m a
+withTimingSilentX logger flag what force k action =
+ withTiming' logger flag what force DontPrintTimings (\_ _ -> k) action
+
+defaultTimingOutput :: MonadIO f => Logger -> PrintTimings -> TimingInfo -> f ()
+defaultTimingOutput logger prtimings tinfo =
+ logTimingInfo logger prtimings tinfo
+ >> dumpTimingInfo logger prtimings tinfo
+
+
+
+logTimingInfo :: MonadIO f => Logger -> PrintTimings -> TimingInfo -> f ()
+logTimingInfo logger prtimings t@(TimingInfo {..}) = do
+ let time = timingMillisecs t
+ when (logVerbAtLeast logger 2 && prtimings == PrintTimings)
+ $ liftIO $ logInfo logger $ withPprStyle defaultUserStyle
+ (text "!!!" <+> timingPhase <> colon <+> text "finished in"
+ <+> doublePrec 2 time
+ <+> text "milliseconds"
+ <> comma
+ <+> text "allocated"
+ <+> doublePrec 3 (realToFrac timingAllocs / 1024 / 1024)
+ <+> text "megabytes")
+
+dumpTimingInfo :: MonadIO m => Logger -> PrintTimings -> TimingInfo -> m ()
+dumpTimingInfo logger prtimings t@(TimingInfo {..}) = do
+ let ctx = log_default_user_context (logFlags logger)
+ whenPrintTimings $
+ putDumpFileMaybe logger Opt_D_dump_timings "" FormatText
+ $ text $ showSDocOneLine ctx
+ $ hsep [ timingPhase <> colon
+ , text "alloc=" <> ppr timingAllocs
+ , text "time=" <> doublePrec 3 (timingMillisecs t)
+ ]
+ where
+ whenPrintTimings = liftIO . when (prtimings == PrintTimings)
-- | Worker for 'withTiming' and 'withTimingSilent'.
withTiming' :: MonadIO m
=> Logger
+ -> DumpFlag
-> SDoc -- ^ The name of the phase
-> (a -> ()) -- ^ A function to force the result
-- (often either @const ()@ or 'rnf')
-> PrintTimings -- ^ Whether to print the timings
+ -> (Logger -> PrintTimings -> TimingInfo -> m ()) -- ^ What to do with all the information after it is collected
-> m a -- ^ The body of the phase to be timed
-> m a
-withTiming' logger what force_result prtimings action
- = if logVerbAtLeast logger 2 || logHasDumpFlag logger Opt_D_dump_timings
+withTiming' logger flag what force_result prtimings output_info action
+ = if logVerbAtLeast logger 2 || logHasDumpFlag logger flag
then do whenPrintTimings $
logInfo logger $ withPprStyle defaultUserStyle $
text "***" <+> what <> colon
let ctx = log_default_user_context (logFlags logger)
alloc0 <- liftIO getAllocationCounter
- start <- liftIO getCPUTime
+ start <- liftIO getCurrentTime
eventBegins ctx what
recordAllocs alloc0
!r <- action
() <- pure $ force_result r
eventEnds ctx what
- end <- liftIO getCPUTime
+ end <- liftIO getCurrentTime
alloc1 <- liftIO getAllocationCounter
recordAllocs alloc1
-- recall that allocation counter counts down
let alloc = alloc0 - alloc1
- time = realToFrac (end - start) * 1e-9
- when (logVerbAtLeast logger 2 && prtimings == PrintTimings)
- $ liftIO $ logInfo logger $ withPprStyle defaultUserStyle
- (text "!!!" <+> what <> colon <+> text "finished in"
- <+> doublePrec 2 time
- <+> text "milliseconds"
- <> comma
- <+> text "allocated"
- <+> doublePrec 3 (realToFrac alloc / 1024 / 1024)
- <+> text "megabytes")
+ output_info logger prtimings (TimingInfo what start end alloc)
- whenPrintTimings $
- putDumpFileMaybe logger Opt_D_dump_timings "" FormatText
- $ text $ showSDocOneLine ctx
- $ hsep [ what <> colon
- , text "alloc=" <> ppr alloc
- , text "time=" <> doublePrec 3 time
- ]
pure r
else action
diff --git a/docs/users_guide/debugging.rst b/docs/users_guide/debugging.rst
index 83d093cd06..1a1791bbbd 100644
--- a/docs/users_guide/debugging.rst
+++ b/docs/users_guide/debugging.rst
@@ -111,6 +111,28 @@ Dumping out compiler intermediate structures
Show allocation and runtime statistics for various stages of compilation.
Allocations are measured in bytes. Timings are measured in milliseconds.
+.. ghc-flag:: -ddump-make-stats
+ :shortdesc: Dump information about the project build time and build graph.
+ :type: dynamic
+
+ Show some statistics about the project build graph after compilation has finished.
+ These can be useful identifying bottlenecks in your projects module structure.
+
+ The statistics which are currently outputted are:
+
+ * The modules which took longest to compile.
+ * The modules which have the largest "flow". The initial flow is 1, and split
+ evenly between all roots of the dependency graph. The flow is propagated
+ through the graph, accumulated on each node and split evenly on children.
+ The result is that any synchronisation points will have a flow equal to 1,
+ and likewise other important modules will have a high flow value.
+ * The length of the longest (critical) path through the project. This provides
+ a lower bound on the projects compilation time.
+ * The "parallelism score" which is the sum of compiling all nodes divided by
+ the length of the critical path. This should be a more stable metric then
+ critical path length because it doesn't depend on how fast your computer is.
+
+
GHC is a large program consisting of a number of stages. You can tell GHC to
dump information from various stages of compilation using the ``-ddump-⟨pass⟩``
flags listed below. Note that some of these tend to produce a lot of output.