{-| This module provides functions to send and receive profiling information over the network. Currently the messages can only encode 'SinkInput' data. -} {-# OPTIONS_GHC -fno-warn-missing-signatures #-} module Profiling.Heap.Network ( Message(..) , sendMsg , recvMsg , readMsg , writeMsg , putStream , getStream ) where import Control.Applicative import Control.Arrow --import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as S import Data.Int import Data.Maybe import Data.List import Profiling.Heap.Types import System.IO data Message = Stream SinkInput sStrSample = "str_sample" sStrName = "str_name" sStrStop = "str_stop" {-| Send a structured message over the network. Can also be used for logging into a file. -} sendMsg :: Handle -> Message -> IO () sendMsg hdl = hPutStrLn hdl . writeMsg {-| Receive a structured message over the network. Can also be used for parsing from a file. -} recvMsg :: Handle -> IO (Maybe Message) recvMsg hdl = readMsg <$> hGetLine hdl {-| Parse a message. -} readMsg :: String -> Maybe Message readMsg = parseString messageParser {-| Serialise a message. -} writeMsg :: Message -> String writeMsg = show {-| Convert from callback data to message. -} putStream :: SinkInput -> Message putStream = Stream {-| Extract callback data from message, if applicable. -} getStream :: Message -> Maybe SinkInput getStream (Stream dat) = Just dat --getStream _ = Nothing instance Show Message where showsPrec _ (Stream (SinkSample t ps)) = showString sStrSample . sepShows t . showPairs ps showsPrec _ (Stream (SinkId ccid name)) = showString sStrName . sepShows ccid . sepStr (S.unpack name) showsPrec _ (Stream SinkStop) = showString sStrStop sep = showChar '\t' sepStr s = sep . showString s sepShows x = sep . shows x showListMap g = foldr (\x f -> sep . g x . f) id showPairs l = showListMap (\(x,y) -> shows x . sepShows y) l -- * A minimal and rather dumb applicative parser, uulib style -- pPref should really be a list of possible prefixes in order to be -- able to implement the choice operator properly, but this simpler -- version is perfectly fine for our purposes. data MsgParser a = MP { pPref :: String, _pFun :: String -> Maybe (a,String) } instance Functor MsgParser where fmap f (MP p g) = MP p ((fmap.fmap) (first f) g) instance Applicative MsgParser where pure x = MP "" (Just . (,) x) MP pf gf <*> MP px gx = MP pf $ \s -> do (f,s') <- gf s s'' <- stripPrefix px s' (x,s''') <- gx s'' return (f x,s''') -- Shady business here: going from bottom to top! instance Alternative MsgParser where empty = MP "" (const Nothing) MP p1 g1 <|> MP p2 g2 = MP "" $ \s -> (stripPrefix p2 s >>= g2) <|> (stripPrefix p1 s >>= g1) infixl 3 <||> -- Alternative with cut (can fail on parseable strings if either p1 or -- p2 is the prefix of the other, but it prevents a space leak if they -- aren't). Yay for past obsession with Prolog. (<||>) :: MsgParser a -> MsgParser a -> MsgParser a MP p1 g1 <||> MP p2 g2 = MP "" $ \s -> case stripPrefix p2 s of Just s' -> g2 s' Nothing -> case stripPrefix p1 s of Just s'' -> g1 s'' Nothing -> Nothing pInt :: MsgParser Int pInt = MP "" $ listToMaybe . reads pInt64 :: MsgParser Int64 pInt64 = MP "" $ listToMaybe . reads pFrac :: MsgParser Double pFrac = MP "" $ listToMaybe . reads --pKey :: MsgParser String --pKey = MP "" $ \str -> -- let (pre,post) = span (`elem` '_':['a'..'z']) str -- in if null pre then Nothing else Just (pre,post) pChr :: Char -> MsgParser Char pChr c = (pure c) { pPref = [c] } pParam :: MsgParser String pParam = MP "" $ Just . span (>=' ') -- This can only be used at the end of a string, because: -- * we assume one delimiter between items (and it can change...) -- * the remainder is not preserved pMany :: MsgParser a -> MsgParser [a] pMany (MP p g) = MP "" $ \str -> let rl s = case g =<< stripPrefix p s of Nothing -> [] Just (v,s') -> v : if null s' then [] else rl (tail s') in Just (rl str,"") infixl 4 <=> infixl 4 <-> (<=>) :: String -> a -> MsgParser a s <=> v = (pure v) { pPref = s } (<->) :: MsgParser (a -> b) -> MsgParser a -> MsgParser b p1 <-> p2 = p1 <* pChr '\t' <*> p2 parseString :: MsgParser a -> String -> Maybe a parseString (MP p g) s = fst <$> (g =<< stripPrefix p s) messageParser :: MsgParser Message messageParser = sStrSample <=> (\t smp -> Stream (SinkSample t smp)) <-> pFrac <-> pProfSample <||> sStrName <=> (\ccid name -> Stream (SinkId ccid name)) <-> pInt <-> (S.pack <$> pParam) <||> sStrStop <=> Stream SinkStop where pProfSample = pMany ((,) <$> pInt <-> pInt64)