1-- | Tracing/Debugging support - Trace Implementation
2module Control.Distributed.Process.Management.Trace.Tracer
3 ( -- * API for the Management Agent
12import Control.Applicative ((<$>))
13import Control.Concurrent.Chan (writeChan)
14import Control.Concurrent.MVar
18import Control.Distributed.Process.Internal.CQueue
21import Control.Distributed.Process.Internal.Primitives
34import Control.Distributed.Process.Management.Types
38import Control.Distributed.Process.Management.Trace.Types
45import Control.Distributed.Process.Management.Trace.Primitives
47import Control.Distributed.Process.Internal.Types
54 , ProcessSignal(NamedSend)
59 , createUnencodedMessage
62import Control.Monad.IO.Class (liftIO)
63import Control.Monad.Reader (ask)
66import qualified Data.Set as Set
68import qualified Data.Map as Map
70import Data.Maybe (fromMaybe)
71import Data.Time.Clock (getCurrentTime)
72import Data.Time.Format (formatTime)
73import Debug.Trace (traceEventIO)
75#if ! MIN_VERSION_base(4,6,0)
76import Prelude hiding (catch)
79import System.Environment (getEnv)
89import System.Locale (defaultTimeLocale)
97 client :: !(Maybe ProcessId)
98 , flags :: !TraceFlags
99 , regNames :: !(Map ProcessId (Set String))
102--------------------------------------------------------------------------------
104--------------------------------------------------------------------------------
106defaultTracer :: Process ()
108 catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_FILE" >>= logfileTracer)
109 (\(_ :: IOError) -> defaultTracerAux)
111defaultTracerAux :: Process ()
113 catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_CONSOLE" >> systemLoggerTracer)
114 (\(_ :: IOError) -> defaultEventLogTracer)
116-- TODO: it would be /nice/ if we had some way of checking the runtime
117-- options to see if +RTS -v (or similar) has been given...
118defaultEventLogTracer :: Process ()
119defaultEventLogTracer =
120 catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_EVENTLOG" >> eventLogTracer)
121 (\(_ :: IOError) -> nullTracer)
123checkEnv :: String -> Process String
124checkEnv s = liftIO $ getEnv s
126-- This trace client is (intentionally) a noop - it simply provides
127-- an intial client for the trace controller to talk to, until some
128-- other (hopefully more useful) client is installed over the top of
129-- it. This is the default trace client.
130nullTracer :: Process ()
132 forever' $ receiveWait [ matchUnknown (return ()) ]
134systemLoggerTracer :: Process ()
135systemLoggerTracer = do
136 node <- processNode <$> ask
137 let tr = sendTraceLog node
138 forever' $ receiveWait [ matchAny (\m -> handleMessage m tr) ]
140 sendTraceLog :: LocalNode -> MxEvent -> Process ()
141 sendTraceLog node ev = do
142 now <- liftIO $ getCurrentTime
143 msg <- return $ (formatTime defaultTimeLocale "%c" now, buildTxt ev)
144 emptyPid <- return $ (nullProcessId (localNodeId node))
145 traceMsg <- return $ NCMsg {
146 ctrlMsgSender = ProcessIdentifier (emptyPid)
147 , ctrlMsgSignal = (NamedSend "logger"
148 (createUnencodedMessage msg))
150 liftIO $ writeChan (localCtrlChan node) traceMsg
152 buildTxt :: MxEvent -> String
153 buildTxt (MxLog msg) = msg
154 buildTxt ev = show ev
156eventLogTracer :: Process ()
158 -- NB: when the GHC event log supports tracing arbitrary (ish) data, we will
159 -- almost certainly use *that* facility independently of whether or not there
160 -- is a tracer process installed. This is just a stop gap until then.
161 forever' $ receiveWait [ matchAny (\m -> handleMessage m writeTrace) ]
163 writeTrace :: MxEvent -> Process ()
164 writeTrace ev = liftIO $ traceEventIO (show ev)
166logfileTracer :: FilePath -> Process ()
168 -- TODO: error handling if the handle cannot be opened
169 h <- liftIO $ openFile p AppendMode
170 liftIO $ hSetBuffering h LineBuffering
171 logger h `finally` (liftIO $ hClose h)
173 logger :: Handle -> Process ()
174 logger h' = forever' $ do
176 matchIf (\ev -> case ev of
177 MxTraceDisable -> True
178 (MxTraceTakeover _) -> True
180 (\_ -> (liftIO $ hClose h') >> die "trace stopped")
181 , matchAny (\ev -> handleMessage ev (writeTrace h'))
184 writeTrace :: Handle -> MxEvent -> Process ()
187 now <- getCurrentTime
188 hPutStrLn h $ (formatTime defaultTimeLocale "%c - " now) ++ (show ev)
190--------------------------------------------------------------------------------
191-- Tracer Implementation --
192--------------------------------------------------------------------------------
194traceController :: MVar ((Weak (CQueue Message))) -> Process ()
195traceController mv = do
196 -- See the documentation for mxAgentController for a
197 -- commentary that explains this breach of encapsulation
198 weakQueue <- processWeakQ <$> ask
199 liftIO $ putMVar mv weakQueue
200 initState <- initialState
201 traceLoop initState { client = Nothing }
203 traceLoop :: TracerState -> Process ()
205 let client' = client st
206 -- Trace events are forwarded to the enabled trace target.
207 -- At some point in the future, we're going to start writing these custom
208 -- events to the ghc eventlog, at which point this design might change.
210 match (\(setResp, set :: SetTrace) -> do
211 -- We consider at most one trace client, which is a process.
212 -- Tracking multiple clients represents too high an overhead,
213 -- so we leave that kind of thing to our consumers (e.g., the
214 -- high level Debug client module) to figure out.
216 (TraceEnable pid) -> do
217 -- notify the previous tracer it has been replaced
218 sendTraceMsg client' (createUnencodedMessage (MxTraceTakeover pid))
220 return st { client = (Just pid) }
222 sendTraceMsg client' (createUnencodedMessage MxTraceDisable)
224 return st { client = Nothing })
225 , match (\(confResp, flags') ->
226 sendOk confResp >> applyTraceFlags flags' st)
227 , match (\chGetFlags -> sendChan chGetFlags (flags st) >> return st)
228 , match (\chGetCurrent -> sendChan chGetCurrent (client st) >> return st)
229 -- we dequeue incoming events even if we don't process them
231 handleMessage ev (handleTrace st ev) >>= return . fromMaybe st)
235 sendOk :: Maybe (SendPort TraceOk) -> Process ()
236 sendOk Nothing = return ()
237 sendOk (Just sp) = sendChan sp TraceOk
239 initialState :: Process TracerState
241 flags' <- checkEnvFlags
242 return $ TracerST { client = Nothing
244 , regNames = Map.empty
247 checkEnvFlags :: Process TraceFlags
249 catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_FLAGS" >>= return . parseFlags)
250 (\(_ :: IOError) -> return defaultTraceFlags)
252 parseFlags :: String -> TraceFlags
253 parseFlags s = parseFlags' s defaultTraceFlags
254 where parseFlags' :: String -> TraceFlags -> TraceFlags
255 parseFlags' [] parsedFlags = parsedFlags
256 parseFlags' (x:xs) parsedFlags
257 | x == 'p' = parseFlags' xs parsedFlags { traceSpawned = traceOn }
258 | x == 'n' = parseFlags' xs parsedFlags { traceRegistered = traceOn }
259 | x == 'u' = parseFlags' xs parsedFlags { traceUnregistered = traceOn }
260 | x == 'd' = parseFlags' xs parsedFlags { traceDied = traceOn }
261 | x == 's' = parseFlags' xs parsedFlags { traceSend = traceOn }
262 | x == 'r' = parseFlags' xs parsedFlags { traceRecv = traceOn }
263 | x == 'l' = parseFlags' xs parsedFlags { traceNodes = True }
264 | otherwise = parseFlags' xs parsedFlags
266applyTraceFlags :: TraceFlags -> TracerState -> Process TracerState
267applyTraceFlags flags' state = return state { flags = flags' }
269handleTrace :: TracerState -> Message -> MxEvent -> Process TracerState
270handleTrace st msg ev@(MxRegistered p n) =
272 Map.insertWith (\_ ns -> Set.insert n ns) p
276 traceEv ev msg (traceRegistered (flags st)) st
277 return st { regNames = regNames' }
278handleTrace st msg ev@(MxUnRegistered p n) =
279 let f ns = case ns of
281 Just ns' -> Just (Set.delete n ns')
282 regNames' = Map.alter f p (regNames st)
284 traceEv ev msg (traceUnregistered (flags st)) st
285 return st { regNames = regNames' }
286handleTrace st msg ev@(MxSpawned _) = do
287 traceEv ev msg (traceSpawned (flags st)) st >> return st
288handleTrace st msg ev@(MxProcessDied _ _) = do
289 traceEv ev msg (traceDied (flags st)) st >> return st
290handleTrace st msg ev@(MxSent _ _ _) =
291 traceEv ev msg (traceSend (flags st)) st >> return st
292handleTrace st msg ev@(MxReceived _ _) =
293 traceEv ev msg (traceRecv (flags st)) st >> return st
294handleTrace st msg ev = do
297 case (traceNodes (flags st)) of
298 True -> sendTrace st ev msg
300 (MxUser _) -> sendTrace st ev msg
301 (MxLog _) -> sendTrace st ev msg
303 case (traceConnections (flags st)) of
304 True -> sendTrace st ev msg
310 -> Maybe TraceSubject
313traceEv _ _ Nothing _ = return ()
314traceEv ev msg (Just TraceAll) st = sendTrace st ev msg
315traceEv ev msg (Just (TraceProcs pids)) st = do
316 node <- processNode <$> ask
317 let p = case resolveToPid ev of
318 Nothing -> (nullProcessId (localNodeId node))
320 case (Set.member p pids) of
321 True -> sendTrace st ev msg
323traceEv ev msg (Just (TraceNames names)) st = do
324 -- if we have recorded regnames for p, then we forward the trace iif
325 -- there are overlapping trace targets
326 node <- processNode <$> ask
327 let p = case resolveToPid ev of
328 Nothing -> (nullProcessId (localNodeId node))
330 case (Map.lookup p (regNames st)) of
332 Just ns -> if (Set.null (Set.intersection ns names))
334 else sendTrace st ev msg
336sendTrace :: TracerState -> MxEvent -> Message -> Process ()
337sendTrace st ev msg = do
339 if c == (resolveToPid ev) -- we do not send the tracer events about itself...
341 else sendTraceMsg c msg
343sendTraceMsg :: Maybe ProcessId -> Message -> Process ()
344sendTraceMsg Nothing _ = return ()
345sendTraceMsg (Just p) msg = (flip forward) p msg
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list