{-| This is a utility module to aid the construction of
'CreateProcess' structures with profiling parameters. -}

module Profiling.Heap.Process 
    ( ProfParam(..)
    , Breakdown(..)
    , Restriction(..)
    , processToProfile
    ) where

import System.Process
import Text.Printf

{-| The possible types of parameters. -}
data ProfParam
    -- | The type of breakdown.
    = PPBreakdown Breakdown
    -- | An additional filter on the runtime side.
    | PPRestriction Restriction [String]
    -- | Sampling interval in seconds.
    | PPInterval Float
    -- | Whether to include memory taken up by threads.
    | PPIncludeThreads
    -- | The maximum length of cost centre stack names.
    | PPNameLength Int
    -- | Retainer set size limit.
    | PPRetainerLimit Int

{-| The possible types of breakdowns. -}
data Breakdown
    -- | Breakdown by cost centre stack (origin of the data).
    = BCostCentreStack
    -- | Breakdown by module (code responsible for the data).
    | BModule
    -- | Breakdown by closure description (constructor name or some
    -- unique identifier).
    | BDescription
    -- | Breakdown by type (or an approximation if it is not known
    -- exactly).
    | BType
    -- | Breakdown by retainer set (effectively the entities that hold
    -- a direct reference to the data in question).
    | BRetainer
    -- | Breakdown by biography (phase of an object's lifetime).
    | BBiography

{-| The possible filters. Note that these are imposed by the runtime,
so we cannot override them on the application side. -}
data Restriction
    -- | Show only closures with one of the given names on the top of
    -- the cost centre stack.
    = RCCStackTop
    -- | Show only closures with one of the given names somewhere in
    -- the cost centre stack.
    | RCCStackAny
    -- | Show only closures produced by one of the given modules.
    | RModule
    -- | Show only closures with a description that matches one of the
    -- given names.
    | RDescription
    -- | Show only closures with one of the given types.
    | RType
    -- | Show only closures with retainer sets that contain at least
    -- one cost centre stack with a given name on the top.
    | RRetainer
    -- | Show only closures with one of the specified biographies,
    -- which must come from the set {lag, drag, void, use}.
    | RBiography

instance Show ProfParam where
    showsPrec _ (PPBreakdown b)      = showString "-h" . shows b
    showsPrec _ (PPRestriction r ns) = showString "-h" . shows r . showsNames ns
    showsPrec _ (PPInterval i)       = showString "-i" . showString (printf "%.2f" i)
    showsPrec _ (PPIncludeThreads)   = showString "-xt"
    showsPrec _ (PPNameLength l)     = showString "-L" . shows l
    showsPrec _ (PPRetainerLimit l)  = showString "-R" . shows l
    showList ps = foldr (\p s -> shows p . showChar ' ' . s) id ps

instance Show Breakdown where
    showsPrec _ BCostCentreStack = showChar 'c'
    showsPrec _ BModule          = showChar 'm'
    showsPrec _ BDescription     = showChar 'd'
    showsPrec _ BType            = showChar 'y'
    showsPrec _ BRetainer        = showChar 'r'
    showsPrec _ BBiography       = showChar 'b'

instance Show Restriction where
    showsPrec _ RCCStackTop  = showChar 'c'
    showsPrec _ RCCStackAny  = showChar 'C'
    showsPrec _ RModule      = showChar 'm'
    showsPrec _ RDescription = showChar 'd'
    showsPrec _ RType        = showChar 'y'
    showsPrec _ RRetainer    = showChar 'r'
    showsPrec _ RBiography   = showChar 'b'

showsNames :: [String] -> String -> String
showsNames []     = id
showsNames [n]    = showString n
showsNames (n:ns) = showString n . showChar ',' . showsNames ns

{-| A helper function to create a 'CreateProcess' structure. -}
processToProfile :: FilePath       -- ^ The executable to profile (relative paths start from the working directory).
                 -> Maybe FilePath -- ^ An optional working directory (inherited from the parent if not given).
                 -> [String]       -- ^ The list of parameters to pass to the program.
                 -> [ProfParam]    -- ^ Profiling parameters.
                 -> CreateProcess  -- ^ The resulting structure.
processToProfile exec dir params profParams = (proc exec allParams) { cwd = dir }
    where -- Note: this doesn't handle --RTS in the param string!
          allParams = params ++ (if null profParams then [] else "+RTS" : map show profParams)