|
| 1 | +{-# LANGUAGE DeriveGeneric #-} |
| 2 | +{-# LANGUAGE FlexibleInstances #-} |
| 3 | +{-# LANGUAGE LambdaCase #-} |
| 4 | +{-# LANGUAGE RecordWildCards #-} |
| 5 | +{-# LANGUAGE ScopedTypeVariables #-} |
| 6 | +{-# LANGUAGE StandaloneDeriving #-} |
| 7 | +{-# LANGUAGE UndecidableInstances #-} |
| 8 | + |
| 9 | +module Cardano.Logging.ForwardingStressTest.Script |
| 10 | + ( TestSetup(..) |
| 11 | + , simpleTestConfig |
| 12 | + , getTestSetup |
| 13 | + , runScriptForwarding |
| 14 | + ) where |
| 15 | + |
| 16 | +import Control.Concurrent (ThreadId, forkFinally, threadDelay) |
| 17 | +import Control.Concurrent.MVar |
| 18 | +import Control.Exception.Base (SomeException, throw) |
| 19 | +import Control.Monad (join, when) |
| 20 | +import Data.Functor ((<&>)) |
| 21 | +import Data.Functor.Identity |
| 22 | +import Data.IORef |
| 23 | +import Data.List (sort) |
| 24 | +import Data.Map (fromList) |
| 25 | +import Data.Maybe |
| 26 | +import Data.Monoid |
| 27 | +import GHC.Generics (Generic) |
| 28 | +import Generic.Data (gmappend) |
| 29 | +import Options.Applicative |
| 30 | +import Ouroboros.Network.Magic (NetworkMagic (..)) |
| 31 | + |
| 32 | +import Test.QuickCheck |
| 33 | + |
| 34 | +import Cardano.Logging |
| 35 | +import Cardano.Logging.Test.Config () |
| 36 | +import Cardano.Logging.Test.Messages |
| 37 | +import Cardano.Logging.Test.Types |
| 38 | + |
| 39 | + |
| 40 | +import Debug.Trace |
| 41 | + |
| 42 | + |
| 43 | +data TestSetup a |
| 44 | + = TestSetup |
| 45 | + { tsTime :: !(a Double) |
| 46 | + , tsThreads :: !(a Int) |
| 47 | + , tsMessages :: !(a (Maybe Int)) |
| 48 | + , tsSocketPath :: !(a FilePath) |
| 49 | + , tsNetworkMagic :: !(a NetworkMagic) |
| 50 | + } deriving (Generic) |
| 51 | +instance Semigroup (TestSetup Last) where |
| 52 | + (<>) = gmappend |
| 53 | + |
| 54 | +deriving instance Show (TestSetup Identity) |
| 55 | + |
| 56 | +defaultTestSetup :: TestSetup Last |
| 57 | +defaultTestSetup = |
| 58 | + TestSetup |
| 59 | + { tsTime = Last $ Just 10.0 |
| 60 | + , tsThreads = Last $ Just 5 |
| 61 | + , tsMessages = Last Nothing |
| 62 | + , tsSocketPath = Last $ Just "/tmp/tracer.sock" |
| 63 | + , tsNetworkMagic = Last $ Just testnetMagic |
| 64 | + } |
| 65 | + where testnetMagic = NetworkMagic 764824073 |
| 66 | + |
| 67 | +parseTestSetup :: Parser (TestSetup Last) |
| 68 | +parseTestSetup = |
| 69 | + TestSetup |
| 70 | + <$> (Last <$> optional (option auto (long "time" <> metavar "SEC"))) |
| 71 | + <*> (Last <$> optional (option auto (long "threads" <> metavar "THRDS"))) |
| 72 | + <*> (Last <$> optional (option auto (long "messages" <> metavar "MSGS"))) |
| 73 | + <*> (Last <$> optional (option auto (long "socket" <> metavar "FILE"))) |
| 74 | + <*> (Last <$> optional (option (NetworkMagic <$> auto) |
| 75 | + (long "network-magic" <> metavar "INT"))) |
| 76 | + |
| 77 | +mergeTestSetup :: TestSetup Last -> TestSetup Identity |
| 78 | +mergeTestSetup TestSetup{..} = |
| 79 | + TestSetup |
| 80 | + { tsTime = get "Missing tsTime" tsTime |
| 81 | + , tsThreads = get "Missing tsThreads" tsThreads |
| 82 | + , tsMessages = Identity . join $ getLast tsMessages |
| 83 | + , tsSocketPath = get "Missing tsSocketPath" tsSocketPath |
| 84 | + , tsNetworkMagic = get "Missing tsNetworkMagic" tsNetworkMagic |
| 85 | + } |
| 86 | + where |
| 87 | + get desc = Identity . fromMaybe (error $ "Missing " <> desc) . getLast |
| 88 | + |
| 89 | +getTestSetup :: IO (TestSetup Identity) |
| 90 | +getTestSetup = |
| 91 | + customExecParser |
| 92 | + (prefs showHelpOnEmpty) |
| 93 | + (info parseTestSetup mempty) |
| 94 | + <&> (defaultTestSetup <>) |
| 95 | + <&> mergeTestSetup |
| 96 | + |
| 97 | +-- | configuration for testing |
| 98 | +simpleTestConfig :: TraceConfig |
| 99 | +simpleTestConfig = emptyTraceConfig { |
| 100 | + tcOptions = fromList |
| 101 | + [([] :: Namespace, |
| 102 | + [ ConfSeverity (SeverityF (Just Debug)) |
| 103 | + , ConfDetail DNormal |
| 104 | + , ConfBackend [Forwarder] |
| 105 | + ]) |
| 106 | + ] |
| 107 | + } |
| 108 | + |
| 109 | +-- | Run scripts in three threads in parallel. |
| 110 | +-- The duration of the test is given by time in seconds |
| 111 | +runScriptForwarding :: |
| 112 | + TestSetup Identity |
| 113 | + -> Trace IO FormattedMessage |
| 114 | + -> Trace IO FormattedMessage |
| 115 | + -> IORef Int |
| 116 | + -> Property |
| 117 | +runScriptForwarding ts@TestSetup{..} fwdTracer stdoutTracer' accumulationCounter = |
| 118 | + trace ("Test setup " ++ show ts) $ do |
| 119 | + let generator :: Gen [Script] = vectorOf (runIdentity tsThreads) $ |
| 120 | + case runIdentity tsMessages of |
| 121 | + Nothing -> scale (* 1000) arbitrary |
| 122 | + Just numMsg -> Script <$> vectorOf numMsg arbitrary |
| 123 | + forAll generator (\ (scripts :: [Script]) |
| 124 | + -> ioProperty $ do |
| 125 | + tr <- mkCardanoTracer |
| 126 | + stdoutTracer' |
| 127 | + fwdTracer |
| 128 | + Nothing |
| 129 | + ["Test"] |
| 130 | + namesForMessage |
| 131 | + severityForMessage |
| 132 | + privacyForMessage |
| 133 | + configureTracers simpleTestConfig docMessage [tr] |
| 134 | + let scripts' = map (\ (Script sc) -> Script |
| 135 | + $ filter (\(ScriptedMessage _ msg) -> |
| 136 | + namesForMessage msg /= ["Message2"]) sc) scripts |
| 137 | + scripts'' = map (\ (Script sc) -> Script (sort sc)) scripts' |
| 138 | + scripts''' = zipWith (\ (Script sc) ind -> Script ( |
| 139 | + withMessageIds (runIdentity tsThreads) ind sc)) scripts'' [0..] |
| 140 | + scripts'''' = map (\ (Script sc) -> Script |
| 141 | + $ map (withTimeFactor (runIdentity tsTime)) sc) scripts''' |
| 142 | + |
| 143 | + |
| 144 | + -- putStrLn ("runTest " ++ show scripts) |
| 145 | + children :: MVar [MVar (Either SomeException ())] <- newMVar [] |
| 146 | + mapM_ (\sc -> forkChild children (playIt sc tr 0.0)) scripts'''' |
| 147 | + res <- waitForChildren children [] |
| 148 | + let resErr = mapMaybe |
| 149 | + (\case |
| 150 | + Right _ -> Nothing |
| 151 | + Left err -> Just err) res |
| 152 | + threadDelay 500000 --wait 0,5 seconds |
| 153 | + if not (null resErr) |
| 154 | + then throw (head resErr) |
| 155 | + else -- Oracle |
| 156 | + let numMsg = sum (map (\ (Script sc) -> length sc) scripts'''') |
| 157 | + in if numMsg > 0 then do |
| 158 | + -- TODO mutiple files |
| 159 | + contents <- readFile "/tmp/cardano-forwarder-test-logs/tmp-tracersock@0/node.json" |
| 160 | + let lineLength = length (lines contents) - 1 |
| 161 | + putStrLn $ "Line length " ++ show lineLength |
| 162 | + putStrLn $ "Msg length " ++ show numMsg |
| 163 | + totalNumMsg <- atomicModifyIORef accumulationCounter (\ac -> |
| 164 | + let nc = ac + numMsg |
| 165 | + in (nc, nc)) |
| 166 | + pure (totalNumMsg == lineLength) |
| 167 | + else do |
| 168 | + putStrLn "Empty test" |
| 169 | + pure True |
| 170 | + |
| 171 | + ) |
| 172 | + |
| 173 | +forkChild :: MVar [MVar (Either SomeException ())] -> IO () -> IO ThreadId |
| 174 | +forkChild children io = do |
| 175 | + mvar <- newEmptyMVar |
| 176 | + childs <- takeMVar children |
| 177 | + putMVar children (mvar:childs) |
| 178 | + forkFinally io (putMVar mvar) |
| 179 | + |
| 180 | +waitForChildren :: MVar [MVar (Either SomeException ())] |
| 181 | + -> [Either SomeException ()] |
| 182 | + -> IO [Either SomeException ()] |
| 183 | +waitForChildren children accum = do |
| 184 | + cs <- takeMVar children |
| 185 | + case cs of |
| 186 | + [] -> pure accum |
| 187 | + m:ms -> do |
| 188 | + putMVar children ms |
| 189 | + res <- takeMVar m |
| 190 | + waitForChildren children (res : accum) |
| 191 | + |
| 192 | + |
| 193 | +-- | Play the current script in one thread |
| 194 | +-- The time is in milliseconds |
| 195 | +playIt :: Script -> Trace IO Message -> Double -> IO () |
| 196 | +playIt (Script []) _tr _d = pure () |
| 197 | +playIt (Script (ScriptedMessage d1 m1 : rest)) tr d = do |
| 198 | + when (d < d1) $ threadDelay (round ((d1 - d) * 1000000)) |
| 199 | + -- this is in microseconds |
| 200 | + traceWith tr m1 |
| 201 | + playIt (Script rest) tr d1 |
| 202 | + |
| 203 | +-- | Adds a message id to every message. |
| 204 | +-- MessageId gives the id to start with. |
| 205 | +-- Returns a tuple with the messages with ids and |
| 206 | +-- the successor of the last used messageId |
| 207 | +withMessageIds :: Int -> MessageID -> [ScriptedMessage] -> [ScriptedMessage] |
| 208 | +withMessageIds numThreads mid sMsgs = go mid sMsgs [] |
| 209 | + where |
| 210 | + go _mid' [] acc = reverse acc |
| 211 | + go mid' (ScriptedMessage time msg : tl) acc = |
| 212 | + go (mid' + numThreads) tl (ScriptedMessage time (setMessageID msg mid') : acc) |
| 213 | + |
| 214 | +withTimeFactor :: Double -> ScriptedMessage -> ScriptedMessage |
| 215 | +withTimeFactor factor (ScriptedMessage time msg) = |
| 216 | + ScriptedMessage (time * factor) msg |
0 commit comments