{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
import Control.Concurrent
import Control.Concurrent.Chan
import Control.Concurrent.MVar
import Control.Monad
import Control.Monad.Fix
import qualified Data.IntMap as IM
import Data.IORef
import Network
import Profiling.Heap.Read
import Profiling.Heap.Process
import Profiling.Heap.Network
import Profiling.Heap.Types
import System.IO
import HandleArgs
-- Start up a process to profile and a server to broadcast the stream
-- to multiple clients.
main = withSocketsDo $ do
(portNum,exec,dir,params) <- relayArgs
let procData = processToProfile exec dir params []
port = PortNumber $ fromIntegral portNum
profChan <- newChan
stopServer <- newEmptyMVar
names <- newIORef IM.empty
cbres <- profileCallback (Local procData) $ \p -> do
-- Broadcasting...
writeChan profChan p
-- Cleaning up the master channel that's not read by
-- anyone.
readChan profChan
case p of
SinkId ccid ccname -> modifyIORef names (IM.insert ccid ccname)
SinkStop -> putMVar stopServer ()
_ -> return ()
case cbres of
Nothing -> putStrLn "Error starting profile reader thread. Did you enable heap profiling?"
Just _ -> runServer port (takeMVar stopServer) $ \chdl -> do
-- A rather lazy way of avoiding the need to maintain an explicit
-- client list and perform additional synchronisation...
ownChan <- dupChan profChan
-- Start by sending the currently known name mapping.
ccmap <- readIORef names
mapM_ (sendMsg chdl . putStream . uncurry SinkId) (IM.toList ccmap)
-- Forward stream to the client.
fix $ \sendLoop -> do
prof <- readChan ownChan
ok <- flip catch (const (return False)) $ do
sendMsg chdl . putStream $ prof
return (prof /= SinkStop)
when ok sendLoop
return ()
-- Start a loop accepting connections and running arbitrary code on
-- them in separate threads, and wait for a stopping action.
runServer port waitForStop act = do
sock <- listenOn port
tid <- forkIO $ forever $ do
(hdl,_host,_cport) <- accept sock
hSetBuffering hdl LineBuffering
forkIO (act hdl)
-- There might be some ugly race conditions here, where clients might
-- be left without a message before leaving the runServer subroutine.
waitForStop
killThread tid