on master help sign in
Add inspectMode combinator

inspectMode adds the ability to turn on diagnostics programmatically. Earlier we had to use a different build for diagnostics. The perf impact is negligible, and mostly on "maxBuffer 1" cases.

commit1f72b61cee165871bde77ea920895274d6e71316 authorHarendra Kumar <harendra.kumar@gmail.com> dateThu 20 Sep 2018, 00:14 (8 years ago) parent0965e59
8 files changed+207−201
.travis.yml+1−1 src/Streamly/Internal.hs+19 src/Streamly/SVar.hs+111−116 src/Streamly/Streams/Ahead.hs+10−23 src/Streamly/Streams/Async.hs+32−20 src/Streamly/Streams/SVar.hs+32−29 stack.yaml+1−3 streamly.cabal+1−9
.travis.yml+1−1
@@ -86,7 +86,7 @@ matrix:
8686 addons: {apt: {packages: [cabal-install-1.24,libsdl1.2-dev], sources: [hvr-ghc]}}
8787
8888 # Use cabal-install-1.24 for stack solver to work
89− - env: BUILD=stack GHCVER=8.2.2 RESOLVER=lts-11 GHC_OPTIONS="" DISABLE_SDIST_BUILD=y DISABLE_DIST_CHECKS=y STACK_BUILD_OPTIONS="--flag streamly:diag"
89+ - env: BUILD=stack GHCVER=8.2.2 RESOLVER=lts-11 GHC_OPTIONS="" DISABLE_SDIST_BUILD=y DISABLE_DIST_CHECKS=y
9090 addons: {apt: {packages: [cabal-install-1.24,libsdl1.2-dev], sources: [hvr-ghc]}}
9191
9292 # Use cabal-install-1.24 for stack solver to work
src/Streamly/Internal.hsnew file+19
@@ -0,0 +1,19 @@
1+-- |
2+-- Module : Streamly.Internal
3+-- Copyright : (c) 2018 Harendra Kumar
4+--
5+-- License : BSD3
6+-- Maintainer : harendra.kumar@gmail.com
7+-- Stability : experimental
8+-- Portability : GHC
9+--
10+-- This module is only for internal use. There is no warranty for the routines
11+-- in this module to work correctly, please use at your own risk. These
12+-- routines are subject to change or be removed without notice.
13+--
14+module Streamly.Internal
15+ ( inspectMode
16+ )
17+where
18+
19+import Streamly.Streams.SVar
src/Streamly/SVar.hs+111−116
@@ -19,11 +19,6 @@
1919-- Maintainer : harendra.kumar@gmail.com
2020-- Stability : experimental
2121-- Portability : GHC
22−--
23−--
24−#ifdef DIAGNOSTICS_VERBOSE
25−#define DIAGNOSTICS
26−#endif
2722
2823module Streamly.SVar
2924 (
@@ -45,6 +40,8 @@ module Streamly.SVar
4540 , setStreamLatency
4641 , getYieldLimit
4742 , setYieldLimit
43+ , getInspectMode
44+ , setInspectMode
4845
4946 , cleanupSVar
5047 , cleanupSVarFromWorker
@@ -99,17 +96,18 @@ module Streamly.SVar
9996 , toStreamVar
10097 , SVarStats (..)
10198 , NanoSecs (..)
102−#ifdef DIAGNOSTICS
10399 , dumpSVar
104−#endif
105100 )
106101where
107102
108103import Control.Concurrent
109104 (ThreadId, myThreadId, threadDelay, throwTo)
110105import Control.Concurrent.MVar
111− (MVar, newEmptyMVar, tryPutMVar, takeMVar, newMVar)
112−import Control.Exception (SomeException(..), catch, mask, assert, Exception)
106+ (MVar, newEmptyMVar, tryPutMVar, takeMVar, newMVar, tryReadMVar)
107+import Control.Exception
108+ (SomeException(..), catch, mask, assert, Exception, catches,
109+ throwIO, Handler(..), BlockedIndefinitelyOnMVar(..),
110+ BlockedIndefinitelyOnSTM(..))
113111import Control.Monad (when)
114112import Control.Monad.Catch (MonadThrow)
115113import Control.Monad.IO.Class (MonadIO(..))
@@ -130,24 +128,12 @@ import GHC.Conc (ThreadId(..))
130128import GHC.Exts
131129import GHC.IO (IO(..))
132130import System.Clock (TimeSpec, Clock(Monotonic), getTime, toNanoSecs)
131+import System.IO (hPutStrLn, stderr)
132+import Text.Printf (printf)
133133
134134import qualified Data.Heap as H
135135import qualified Data.Set as S
136136
137−-- MVar diagnostics has some overhead - around 5% on asyncly null benchmark, we
138−-- can keep it on in production to debug problems quickly if and when they
139−-- happen, but it may result in unexpected output when threads are left hanging
140−-- until they are GCed because the consumer went away.
141−
142−#ifdef DIAGNOSTICS
143−import Control.Concurrent.MVar (tryTakeMVar)
144−import Control.Exception
145− (catches, throwIO, Handler(..), BlockedIndefinitelyOnMVar(..),
146− BlockedIndefinitelyOnSTM(..))
147−import System.IO (hPutStrLn, stderr)
148−import Text.Printf (printf)
149−#endif
150−
151137-- Always use signed arithmetic to avoid inadvertant overflows of signed values
152138-- on conversion when comparing unsigned quantities with signed.
153139newtype NanoSecs = NanoSecs Int64
@@ -371,13 +357,14 @@ data SVar t m a = SVar
371357 , svarStats :: SVarStats
372358 -- to track garbage collection of SVar
373359 , svarRef :: Maybe (IORef ())
374−#ifdef DIAGNOSTICS
360+
361+ -- Only for diagnostics
362+ , svarInspectMode :: Bool
375363 , svarCreator :: ThreadId
376364 , outputHeap :: IORef ( Heap (Entry Int (AheadHeapEntry t m a))
377365 , Maybe Int)
378366 -- Shared work queue (stream, seqNo)
379367 , aheadWorkQueue :: IORef ([t m a], Int)
380−#endif
381368 }
382369
383370-------------------------------------------------------------------------------
@@ -401,6 +388,7 @@ data State t m a = State
401388 -- XXX these two can be collapsed into a single type
402389 , _streamLatency :: Maybe NanoSecs -- bootstrap latency
403390 , _maxStreamRate :: Maybe Rate
391+ , _inspectMode :: Bool
404392 }
405393
406394-------------------------------------------------------------------------------
@@ -428,6 +416,7 @@ defState = State
428416 , _bufferHigh = defaultMaxBuffer
429417 , _maxStreamRate = Nothing
430418 , _streamLatency = Nothing
419+ , _inspectMode = False
431420 }
432421
433422-- XXX if perf gets affected we can have all the Nothing params in a single
@@ -505,6 +494,12 @@ setStreamLatency n st =
505494getStreamLatency :: State t m a -> Maybe NanoSecs
506495getStreamLatency = _streamLatency
507496
497+setInspectMode :: State t m a -> State t m a
498+setInspectMode st = st { _inspectMode = True }
499+
500+getInspectMode :: State t m a -> Bool
501+getInspectMode = _inspectMode
502+
508503-------------------------------------------------------------------------------
509504-- Cleanup
510505-------------------------------------------------------------------------------
@@ -526,7 +521,6 @@ cleanupSVarFromWorker sv = do
526521-- Dumping the SVar for debug/diag
527522-------------------------------------------------------------------------------
528523
529−#ifdef DIAGNOSTICS
530524-- | Convert a number of seconds to a string. The string will consist
531525-- of four decimal places, followed by a short description of the time
532526-- units.
@@ -553,8 +547,8 @@ secs k
553547 | otherwise = printf "%.3f %s" t u
554548
555549-- XXX Code duplicated from collectLatency
556−drainLatency :: SVarStats -> YieldRateInfo -> IO (Count, TimeSpec, NanoSecs)
557−drainLatency _ss yinfo = do
550+drainLatency :: SVar t m a -> YieldRateInfo -> IO (Count, TimeSpec, NanoSecs)
551+drainLatency sv yinfo = do
558552 let cur = workerPendingLatency yinfo
559553 col = workerCollectedLatency yinfo
560554 longTerm = svarAllTimeLatency yinfo
@@ -574,21 +568,19 @@ drainLatency _ss yinfo = do
574568 if (pendingCount > 0)
575569 then do
576570 let new = pendingTime `div` (fromIntegral pendingCount)
577−#ifdef DIAGNOSTICS
578− minLat <- readIORef (minWorkerLatency _ss)
579− when (new < minLat || minLat == 0) $
580− writeIORef (minWorkerLatency _ss) new
581−
582− maxLat <- readIORef (maxWorkerLatency _ss)
583− when (new > maxLat) $ writeIORef (maxWorkerLatency _ss) new
584−#endif
571+ when (svarInspectMode sv) $ do
572+ let ss = svarStats sv
573+ minLat <- readIORef (minWorkerLatency ss)
574+ when (new < minLat || minLat == 0) $
575+ writeIORef (minWorkerLatency ss) new
576+
577+ maxLat <- readIORef (maxWorkerLatency ss)
578+ when (new > maxLat) $ writeIORef (maxWorkerLatency ss) new
579+ modifyIORef (avgWorkerLatency ss) $
580+ \(cnt, t) -> (cnt + pendingCount, t + pendingTime)
585581 -- To avoid minor fluctuations update in batches
586582 writeIORef col (0, 0)
587583 writeIORef measured new
588−#ifdef DIAGNOSTICS
589− modifyIORef (avgWorkerLatency _ss) $
590− \(cnt, t) -> (cnt + pendingCount, t + pendingTime)
591−#endif
592584 modifyIORef longTerm $ \(_, t) -> (lcount', t)
593585 return (lcount', ltime, new)
594586 else return notUpdated
@@ -598,7 +590,7 @@ dumpSVarStats sv ss style = do
598590 case yieldRateInfo sv of
599591 Nothing -> return ()
600592 Just yinfo -> do
601− _ <- liftIO $ drainLatency (svarStats sv) yinfo
593+ _ <- liftIO $ drainLatency sv yinfo
602594 return ()
603595
604596 dispatches <- readIORef $ totalDispatches ss
@@ -662,7 +654,7 @@ dumpSVarStats sv ss style = do
662654dumpSVar :: SVar t m a -> IO String
663655dumpSVar sv = do
664656 (oqList, oqLen) <- readIORef $ outputQueue sv
665− db <- tryTakeMVar $ outputDoorBell sv
657+ db <- tryReadMVar $ outputDoorBell sv
666658 aheadDump <-
667659 if svarStyle sv == AheadVar
668660 then do
@@ -686,15 +678,17 @@ dumpSVar sv = do
686678 stats <- dumpSVarStats sv (svarStats sv) (svarStyle sv)
687679
688680 return $ unlines
689− [ "Creator tid = " ++ show (svarCreator sv)
690− , "style = " ++ show (svarStyle sv)
681+ [
682+ "Creator tid = " ++ show (svarCreator sv),
683+ "style = " ++ show (svarStyle sv)
691684 , "---------CURRENT STATE-----------"
692685 , "outputQueue length computed = " ++ show (length oqList)
693686 , "outputQueue length maintained = " ++ show oqLen
694687 -- XXX print the types of events in the outputQueue, first 5
695688 , "outputDoorBell = " ++ show db
696689 ]
697− ++ aheadDump ++ unlines
690+ ++ aheadDump
691+ ++ unlines
698692 [ "needDoorBell = " ++ show waiting
699693 , "running threads = " ++ show rthread
700694 -- XXX print the status of first 5 threads
@@ -703,6 +697,11 @@ dumpSVar sv = do
703697 ++ "---------STATS-----------\n"
704698 ++ stats
705699
700+-- MVar diagnostics has some overhead - around 5% on asyncly null benchmark, we
701+-- can keep it on in production to debug problems quickly if and when they
702+-- happen, but it may result in unexpected output when threads are left hanging
703+-- until they are GCed because the consumer went away.
704+
706705{-# NOINLINE mvarExcHandler #-}
707706mvarExcHandler :: SVar t m a -> String -> BlockedIndefinitelyOnMVar -> IO ()
708707mvarExcHandler sv label e@BlockedIndefinitelyOnMVar = do
@@ -717,15 +716,14 @@ stmExcHandler sv label e@BlockedIndefinitelyOnSTM = do
717716 hPutStrLn stderr $ label ++ " " ++ "BlockedIndefinitelyOnSTM\n" ++ svInfo
718717 throwIO e
719718
720−withDBGMVar :: SVar t m a -> String -> IO () -> IO ()
721−withDBGMVar sv label action =
722− action `catches` [ Handler (mvarExcHandler sv label)
723− , Handler (stmExcHandler sv label)
724− ]
725−#else
726−withDBGMVar :: SVar t m a -> String -> IO () -> IO ()
727−withDBGMVar _ _ action = action
728−#endif
719+withDiagMVar :: SVar t m a -> String -> IO () -> IO ()
720+withDiagMVar sv label action =
721+ if svarInspectMode sv
722+ then
723+ action `catches` [ Handler (mvarExcHandler sv label)
724+ , Handler (stmExcHandler sv label)
725+ ]
726+ else action
729727
730728-------------------------------------------------------------------------------
731729-- CAS
@@ -1248,22 +1246,19 @@ handleChildException sv e = do
12481246 tid <- myThreadId
12491247 void $ send sv (ChildStop tid (Just e))
12501248
1251−#ifdef DIAGNOSTICS
1249+{-# NOINLINE recordMaxWorkers #-}
12521250recordMaxWorkers :: MonadIO m => SVar t m a -> m ()
12531251recordMaxWorkers sv = liftIO $ do
12541252 active <- readIORef (workerCount sv)
12551253 maxWrk <- readIORef (maxWorkers $ svarStats sv)
12561254 when (active > maxWrk) $ writeIORef (maxWorkers $ svarStats sv) active
12571255 modifyIORef (totalDispatches $ svarStats sv) (+1)
1258−#endif
12591256
12601257{-# NOINLINE pushWorker #-}
12611258pushWorker :: MonadAsync m => Count -> SVar t m a -> m ()
12621259pushWorker yieldMax sv = do
12631260 liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
1264−#ifdef DIAGNOSTICS
1265− recordMaxWorkers sv
1266−#endif
1261+ when (svarInspectMode sv) $ recordMaxWorkers sv
12671262 -- This allocation matters when significant number of workers are being
12681263 -- sent. We allocate it only when needed.
12691264 winfo <-
@@ -1288,34 +1283,39 @@ pushWorker yieldMax sv = do
12881283-- producer side. So we need to use a thread safe modification of
12891284-- workerThreads. Alternatively, we can use a CreateThread event to avoid
12901285-- using a CAS based modification.
1291−{-# NOINLINE pushWorkerPar #-}
1292−pushWorkerPar :: MonadAsync m => SVar t m a -> (Maybe WorkerInfo -> m ()) -> m ()
1286+{-# INLINE pushWorkerPar #-}
1287+pushWorkerPar
1288+ :: MonadAsync m
1289+ => SVar t m a -> (Maybe WorkerInfo -> m ()) -> m ()
12931290pushWorkerPar sv wloop =
1294− -- We do not use workerCount in case of ParallelVar but still there is no
1295− -- harm in maintaining it correctly.
1296−#ifdef DIAGNOSTICS
1297− do
1298− liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
1299− recordMaxWorkers sv
1300− -- This allocation matters when significant number of workers are being
1301− -- sent. We allocate it only when needed. The overhead increases by 4x.
1302− winfo <-
1303− case yieldRateInfo sv of
1304− Nothing -> return Nothing
1305− Just _ -> liftIO $ do
1306− cntRef <- newIORef 0
1307− t <- getTime Monotonic
1308− lat <- newIORef (0, t)
1309− return $ Just $ WorkerInfo
1310− { workerYieldMax = 0
1311− , workerYieldCount = cntRef
1312− , workerLatencyStart = lat
1313− }
1291+ if svarInspectMode sv
1292+ then forkWithDiag
1293+ else doFork (wloop Nothing) (handleChildException sv) >>= modifyThread sv
13141294
1315− doFork (wloop winfo) (handleChildException sv) >>= modifyThread sv
1316−#else
1317− doFork (wloop Nothing) (handleChildException sv) >>= modifyThread sv
1318−#endif
1295+ where
1296+
1297+ {-# NOINLINE forkWithDiag #-}
1298+ forkWithDiag = do
1299+ -- We do not use workerCount in case of ParallelVar but still there is
1300+ -- no harm in maintaining it correctly.
1301+ liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
1302+ recordMaxWorkers sv
1303+ -- This allocation matters when significant number of workers are being
1304+ -- sent. We allocate it only when needed. The overhead increases by 4x.
1305+ winfo <-
1306+ case yieldRateInfo sv of
1307+ Nothing -> return Nothing
1308+ Just _ -> liftIO $ do
1309+ cntRef <- newIORef 0
1310+ t <- getTime Monotonic
1311+ lat <- newIORef (0, t)
1312+ return $ Just $ WorkerInfo
1313+ { workerYieldMax = 0
1314+ , workerYieldCount = cntRef
1315+ , workerLatencyStart = lat
1316+ }
1317+
1318+ doFork (wloop winfo) (handleChildException sv) >>= modifyThread sv
13191319
13201320-- Returns:
13211321-- True: can dispatch more
@@ -1547,8 +1547,8 @@ updateWorkerPollingInterval yinfo latency = do
15471547-- whereas the average is used for future estimates e.g. how many workers
15481548-- should be maintained to maintain the rate.
15491549-- CAUTION! keep it in sync with getWorkerLatency
1550−collectLatency :: SVarStats -> YieldRateInfo -> IO (Count, TimeSpec, NanoSecs)
1551−collectLatency _ss yinfo = do
1550+collectLatency :: SVar t m a -> YieldRateInfo -> IO (Count, TimeSpec, NanoSecs)
1551+collectLatency sv yinfo = do
15521552 let cur = workerPendingLatency yinfo
15531553 col = workerCollectedLatency yinfo
15541554 longTerm = svarAllTimeLatency yinfo
@@ -1567,15 +1567,15 @@ collectLatency _ss yinfo = do
15671567
15681568 if pendingCount > 0
15691569 then do
1570− let new = pendingTime `div` fromIntegral pendingCount
1571−#ifdef DIAGNOSTICS
1572− minLat <- readIORef (minWorkerLatency _ss)
1573− when (new < minLat || minLat == 0) $
1574− writeIORef (minWorkerLatency _ss) new
1575−
1576− maxLat <- readIORef (maxWorkerLatency _ss)
1577− when (new > maxLat) $ writeIORef (maxWorkerLatency _ss) new
1578−#endif
1570+ let new = pendingTime `div` (fromIntegral pendingCount)
1571+ when (svarInspectMode sv) $ do
1572+ let ss = svarStats sv
1573+ minLat <- readIORef (minWorkerLatency ss)
1574+ when (new < minLat || minLat == 0) $
1575+ writeIORef (minWorkerLatency ss) new
1576+
1577+ maxLat <- readIORef (maxWorkerLatency ss)
1578+ when (new > maxLat) $ writeIORef (maxWorkerLatency ss) new
15791579 -- When we have collected a significant sized batch we compute the new
15801580 -- latency using that batch and return the new latency, otherwise we
15811581 -- return the previous latency derived from the previous batch.
@@ -1585,13 +1585,13 @@ collectLatency _ss yinfo = do
15851585 in prev > 0 && (r > 2 || r < 0.5))
15861586 || (prev == 0)
15871587 then do
1588+ when (svarInspectMode sv) $ do
1589+ let ss = svarStats sv
1590+ modifyIORef (avgWorkerLatency ss) $
1591+ \(cnt, t) -> (cnt + pendingCount, t + pendingTime)
15881592 updateWorkerPollingInterval yinfo (max new prev)
15891593 writeIORef col (0, 0)
15901594 writeIORef measured ((prev + new) `div` 2)
1591−#ifdef DIAGNOSTICS
1592− modifyIORef (avgWorkerLatency _ss) $
1593− \(cnt, t) -> (cnt + pendingCount, t + pendingTime)
1594−#endif
15951595 modifyIORef longTerm $ \(_, t) -> (lcount', t)
15961596 return $ tripleWith new
15971597 else do
@@ -1613,7 +1613,7 @@ dispatchWorkerPaced sv = do
16131613 (svarYields, svarElapsed, wLatency) <- do
16141614 now <- liftIO $ getTime Monotonic
16151615 (yieldCount, baseTime, lat) <-
1616− liftIO $ collectLatency (svarStats sv) yinfo
1616+ liftIO $ collectLatency sv yinfo
16171617 let elapsed = fromInteger $ toNanoSecs $ now - baseTime
16181618 let latency =
16191619 if lat == 0
@@ -1811,7 +1811,7 @@ sendWorkerWait delay dispatch sv = do
18111811 if canDoMore
18121812 then sendWorkerWait delay dispatch sv
18131813 else do
1814− liftIO $ withDBGMVar sv "sendWorkerWait: nothing to do"
1814+ liftIO $ withDiagMVar sv "sendWorkerWait: nothing to do"
18151815 $ takeMVar (outputDoorBell sv)
18161816 (_, len) <- liftIO $ readIORef (outputQueue sv)
18171817 when (len <= 0) $ sendWorkerWait delay dispatch sv
@@ -1820,10 +1820,10 @@ sendWorkerWait delay dispatch sv = do
18201820readOutputQRaw :: SVar t m a -> IO ([ChildEvent a], Int)
18211821readOutputQRaw sv = do
18221822 (list, len) <- atomicModifyIORefCAS (outputQueue sv) $ \x -> (([],0), x)
1823−#ifdef DIAGNOSTICS
1824− oqLen <- readIORef (maxOutQSize $ svarStats sv)
1825− when (len > oqLen) $ writeIORef (maxOutQSize $ svarStats sv) len
1826−#endif
1823+ when (svarInspectMode sv) $ do
1824+ let ref = maxOutQSize $ svarStats sv
1825+ oqLen <- readIORef ref
1826+ when (len > oqLen) $ writeIORef ref len
18271827 return (list, len)
18281828
18291829readOutputQBounded :: MonadAsync m => SVar t m a -> m [ChildEvent a]
@@ -1985,9 +1985,7 @@ getAheadSVar st f = do
19851985 maxLat <- newIORef (NanoSecs 0)
19861986 minLat <- newIORef (NanoSecs 0)
19871987 stpTime <- newIORef Nothing
1988−#ifdef DIAGNOSTICS
19891988 tid <- myThreadId
1990−#endif
19911989
19921990 let getSVar sv readOutput postProc = SVar
19931991 { outputQueue = outQ
@@ -2009,11 +2007,10 @@ getAheadSVar st f = do
20092007 , accountThread = delThread sv
20102008 , workerStopMVar = stopMVar
20112009 , svarRef = Nothing
2012−#ifdef DIAGNOSTICS
2010+ , svarInspectMode = getInspectMode st
20132011 , svarCreator = tid
20142012 , aheadWorkQueue = q
20152013 , outputHeap = outH
2016−#endif
20172014 , svarStats = SVarStats
20182015 { totalDispatches = disp
20192016 , maxWorkers = maxWrk
@@ -2081,9 +2078,7 @@ getParallelSVar st = do
20812078 maxLat <- newIORef (NanoSecs 0)
20822079 minLat <- newIORef (NanoSecs 0)
20832080 stpTime <- newIORef Nothing
2084−#ifdef DIAGNOSTICS
20852081 tid <- myThreadId
2086−#endif
20872082
20882083 let sv =
20892084 SVar { outputQueue = outQ
@@ -2106,11 +2101,10 @@ getParallelSVar st = do
21062101 , accountThread = modifyThread sv
21072102 , workerStopMVar = undefined
21082103 , svarRef = Nothing
2109−#ifdef DIAGNOSTICS
2104+ , svarInspectMode = getInspectMode st
21102105 , svarCreator = tid
21112106 , aheadWorkQueue = undefined
21122107 , outputHeap = undefined
2113−#endif
21142108 , svarStats = SVarStats
21152109 { totalDispatches = disp
21162110 , maxWorkers = maxWrk
@@ -2128,10 +2122,11 @@ getParallelSVar st = do
21282122 where
21292123
21302124 readOutputQPar sv = liftIO $ do
2131− withDBGMVar sv "readOutputQPar: doorbell" $ takeMVar (outputDoorBell sv)
2125+ withDiagMVar sv "readOutputQPar: doorbell"
2126+ $ takeMVar (outputDoorBell sv)
21322127 case yieldRateInfo sv of
21332128 Nothing -> return ()
2134− Just yinfo -> void $ collectLatency (svarStats sv) yinfo
2129+ Just yinfo -> void $ collectLatency sv yinfo
21352130 fst `fmap` readOutputQRaw sv
21362131
21372132sendFirstWorker :: MonadAsync m => SVar t m a -> t m a -> m (SVar t m a)
src/Streamly/Streams/Ahead.hs+10−23
@@ -8,10 +8,6 @@
88{-# LANGUAGE StandaloneDeriving #-}
99{-# LANGUAGE UndecidableInstances #-} -- XXX
1010
11−#ifdef DIAGNOSTICS_VERBOSE
12−#define DIAGNOSTICS
13−#endif
14−
1511-- |
1612-- Module : Streamly.Streams.Ahead
1713-- Copyright : (c) 2017 Harendra Kumar
@@ -32,7 +28,7 @@ module Streamly.Streams.Ahead
3228where
3329
3430import Control.Concurrent.MVar (putMVar, takeMVar)
35−import Control.Monad (ap, void)
31+import Control.Monad (ap, void, when)
3632import Control.Monad.Base (MonadBase(..), liftBaseDefault)
3733import Control.Monad.Catch (MonadThrow, throwM)
3834-- import Control.Monad.Error.Class (MonadError(..))
@@ -41,7 +37,7 @@ import Control.Monad.Reader.Class (MonadReader(..))
4137import Control.Monad.State.Class (MonadState(..))
4238import Control.Monad.Trans.Class (MonadTrans(lift))
4339import Data.Heap (Heap, Entry(..))
44−import Data.IORef (IORef, readIORef, atomicModifyIORef)
40+import Data.IORef (IORef, readIORef, atomicModifyIORef, writeIORef)
4541import Data.Maybe (fromJust)
4642import Data.Semigroup (Semigroup(..))
4743import GHC.Exts (inline)
@@ -54,10 +50,6 @@ import Streamly.SVar
5450import Streamly.Streams.StreamK (IsStream(..), Stream(..))
5551import qualified Streamly.Streams.StreamK as K
5652
57−#ifdef DIAGNOSTICS
58−import Control.Monad (when)
59−import Data.IORef (writeIORef)
60−#endif
6153import Prelude hiding (map)
6254
6355#include "Instances.hs"
@@ -360,12 +352,12 @@ processWithoutToken q heap st sv winfo m sno = do
360352 let hp' = H.insert (Entry seqNo ent) hp
361353 in ((hp', snum), hp')
362354
363−#ifdef DIAGNOSTICS
364− liftIO $ do
365− maxHp <- readIORef (maxHeapSize $ svarStats sv)
366− when (H.size newHp > maxHp) $
367− writeIORef (maxHeapSize $ svarStats sv) (H.size newHp)
368−#endif
355+ when (svarInspectMode sv) $
356+ liftIO $ do
357+ maxHp <- readIORef (maxHeapSize $ svarStats sv)
358+ when (H.size newHp > maxHp) $
359+ writeIORef (maxHeapSize $ svarStats sv) (H.size newHp)
360+
369361 heapOk <- liftIO $ underMaxHeap sv newHp
370362 let drainAndStop = drainHeap q heap st sv winfo
371363 mainLoop = workLoopAhead q heap st sv winfo
@@ -486,6 +478,8 @@ processWithToken q heap st sv winfo action sno = do
486478-- hooks can be used for a more general implementation to even check predicates
487479-- and not just yield limit.
488480
481+-- XXX we can remove the sv parameter as it can be derived from st
482+
489483workLoopAhead :: MonadIO m
490484 => IORef ([Stream m a], Int)
491485 -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)), Maybe Int)
@@ -494,13 +488,6 @@ workLoopAhead :: MonadIO m
494488 -> Maybe WorkerInfo
495489 -> m ()
496490workLoopAhead q heap st sv winfo = do
497−#ifdef DIAGNOSTICS
498− liftIO $ do
499− maxHp <- readIORef (maxHeapSize $ svarStats sv)
500− (hp, _) <- readIORef heap
501− when (H.size hp > maxHp) $ writeIORef (maxHeapSize $ svarStats sv)
502− (H.size hp)
503−#endif
504491 r <- liftIO $ dequeueFromHeap heap
505492 case r of
506493 Ready (Entry seqNo hent) ->
src/Streamly/Streams/Async.hs+32−20
@@ -6,13 +6,10 @@
66{-# LANGUAGE InstanceSigs #-}
77{-# LANGUAGE LambdaCase #-}
88{-# LANGUAGE MultiParamTypeClasses #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
910{-# LANGUAGE StandaloneDeriving #-}
1011{-# LANGUAGE UndecidableInstances #-} -- XXX
1112
12−#ifdef DIAGNOSTICS_VERBOSE
13−#define DIAGNOSTICS
14−#endif
15−
1613-- |
1714-- Module : Streamly.Streams.Async
1815-- Copyright : (c) 2017 Harendra Kumar
@@ -40,6 +37,7 @@ module Streamly.Streams.Async
4037 )
4138where
4239
40+import Control.Concurrent (myThreadId)
4341import Control.Monad (ap)
4442import Control.Monad.Base (MonadBase(..), liftBaseDefault)
4543import Control.Monad.Catch (MonadThrow, throwM)
@@ -63,10 +61,6 @@ import Streamly.SVar
6361import Streamly.Streams.StreamK (IsStream(..), Stream(..), adapt)
6462import qualified Streamly.Streams.StreamK as K
6563
66−#ifdef DIAGNOSTICS
67−import Control.Concurrent (myThreadId)
68−#endif
69−
7064#include "Instances.hs"
7165
7266-------------------------------------------------------------------------------
@@ -172,6 +166,8 @@ workLoopLIFOLimited q st sv winfo = run
172166-- WAsync
173167-------------------------------------------------------------------------------
174168
169+-- XXX we can remove sv as it is derivable from st
170+
175171{-# INLINE workLoopFIFO #-}
176172workLoopFIFO
177173 :: MonadIO m
@@ -253,7 +249,8 @@ workLoopFIFOLimited q st sv winfo = run
253249-- function argument to this function results in a perf degradation of more
254250-- than 10%. Need to investigate what the root cause is.
255251-- Interestingly, the same thing does not make any difference for Ahead.
256−getLifoSVar :: MonadAsync m => State Stream m a -> IO (SVar Stream m a)
252+getLifoSVar :: forall m a. MonadAsync m
253+ => State Stream m a -> IO (SVar Stream m a)
257254getLifoSVar st = do
258255 outQ <- newIORef ([], 0)
259256 outQMv <- newEmptyMVar
@@ -275,9 +272,7 @@ getLifoSVar st = do
275272 maxLat <- newIORef (NanoSecs 0)
276273 minLat <- newIORef (NanoSecs 0)
277274 stpTime <- newIORef Nothing
278−#ifdef DIAGNOSTICS
279275 tid <- myThreadId
280−#endif
281276
282277 let isWorkFinished _ = null <$> readIORef q
283278
@@ -291,7 +286,17 @@ getLifoSVar st = do
291286 qEmpty <- null <$> readIORef q
292287 return $ qEmpty || yieldsDone
293288
294− let getSVar sv readOutput postProc workDone wloop = SVar
289+ let getSVar :: SVar Stream m a
290+ -> (SVar Stream m a -> m [ChildEvent a])
291+ -> (SVar Stream m a -> m Bool)
292+ -> (SVar Stream m a -> IO Bool)
293+ -> (IORef [Stream m a]
294+ -> State Stream m a
295+ -> SVar Stream m a
296+ -> Maybe WorkerInfo
297+ -> m())
298+ -> SVar Stream m a
299+ getSVar sv readOutput postProc workDone wloop = SVar
295300 { outputQueue = outQ
296301 , remainingWork = yl
297302 , maxBufferLimit = getMaxBuffer st
@@ -311,11 +316,10 @@ getLifoSVar st = do
311316 , accountThread = delThread sv
312317 , workerStopMVar = undefined
313318 , svarRef = Nothing
314−#ifdef DIAGNOSTICS
319+ , svarInspectMode = getInspectMode st
315320 , svarCreator = tid
316321 , aheadWorkQueue = undefined
317322 , outputHeap = undefined
318−#endif
319323 , svarStats = SVarStats
320324 { totalDispatches = disp
321325 , maxWorkers = maxWrk
@@ -353,7 +357,8 @@ getLifoSVar st = do
353357 workLoopLIFOLimited
354358 in return sv
355359
356−getFifoSVar :: MonadAsync m => State Stream m a -> IO (SVar Stream m a)
360+getFifoSVar :: forall m a. MonadAsync m
361+ => State Stream m a -> IO (SVar Stream m a)
357362getFifoSVar st = do
358363 outQ <- newIORef ([], 0)
359364 outQMv <- newEmptyMVar
@@ -375,9 +380,7 @@ getFifoSVar st = do
375380 maxLat <- newIORef (NanoSecs 0)
376381 minLat <- newIORef (NanoSecs 0)
377382 stpTime <- newIORef Nothing
378−#ifdef DIAGNOSTICS
379383 tid <- myThreadId
380−#endif
381384
382385 let isWorkFinished _ = nullQ q
383386 let isWorkFinishedLimited sv = do
@@ -390,7 +393,17 @@ getFifoSVar st = do
390393 qEmpty <- nullQ q
391394 return $ qEmpty || yieldsDone
392395
393− let getSVar sv readOutput postProc workDone wloop = SVar
396+ let getSVar :: SVar Stream m a
397+ -> (SVar Stream m a -> m [ChildEvent a])
398+ -> (SVar Stream m a -> m Bool)
399+ -> (SVar Stream m a -> IO Bool)
400+ -> (LinkedQueue (Stream m a)
401+ -> State Stream m a
402+ -> SVar Stream m a
403+ -> Maybe WorkerInfo
404+ -> m())
405+ -> SVar Stream m a
406+ getSVar sv readOutput postProc workDone wloop = SVar
394407 { outputQueue = outQ
395408 , remainingWork = yl
396409 , maxBufferLimit = getMaxBuffer st
@@ -410,11 +423,10 @@ getFifoSVar st = do
410423 , accountThread = delThread sv
411424 , workerStopMVar = undefined
412425 , svarRef = Nothing
413−#ifdef DIAGNOSTICS
426+ , svarInspectMode = getInspectMode st
414427 , svarCreator = tid
415428 , aheadWorkQueue = undefined
416429 , outputHeap = undefined
417−#endif
418430 , svarStats = SVarStats
419431 { totalDispatches = disp
420432 , maxWorkers = maxWrk
src/Streamly/Streams/SVar.hs+32−29
@@ -11,10 +11,6 @@
1111
1212#include "inline.h"
1313
14−#ifdef DIAGNOSTICS_VERBOSE
15−#define DIAGNOSTICS
16−#endif
17−
1814-- |
1915-- Module : Streamly.Streams.SVar
2016-- Copyright : (c) 2017 Harendra Kumar
@@ -37,37 +33,38 @@ module Streamly.Streams.SVar
3733 , minRate
3834 , maxRate
3935 , constRate
36+ , inspectMode
37+ , printState
4038 )
4139where
4240
4341import Control.Exception (fromException)
42+import Control.Monad (when)
4443import Control.Monad.Catch (throwM)
44+import Control.Monad.IO.Class (MonadIO(liftIO))
4545import Data.Int (Int64)
46−import Control.Monad.IO.Class (liftIO)
47−import Data.IORef (newIORef, mkWeakIORef)
48−#ifdef DIAGNOSTICS
46+import Data.IORef (newIORef, readIORef, mkWeakIORef)
4947import Data.IORef (writeIORef)
48+import Data.Maybe (isNothing)
5049import System.IO (hPutStrLn, stderr)
5150import System.Clock (Clock(Monotonic), getTime)
52−#endif
51+import System.Mem (performMajorGC)
5352
5453import Streamly.SVar
5554import Streamly.Streams.StreamK
5655import Streamly.Streams.Serial (SerialT)
5756
58−-- MVar diagnostics has some overhead - around 5% on asyncly null benchmark, we
59−-- can keep it on in production to debug problems quickly if and when they
60−-- happen, but it may result in unexpected output when threads are left hanging
61−-- until they are GCed because the consumer went away.
62−
63−#ifdef DIAGNOSTICS
64−#ifdef DIAGNOSTICS_VERBOSE
6557printSVar :: SVar t m a -> String -> IO ()
6658printSVar sv how = do
6759 svInfo <- dumpSVar sv
6860 hPutStrLn stderr $ "\n" ++ how ++ "\n" ++ svInfo
69−#endif
70−#endif
61+
62+printState :: MonadIO m => State Stream m a -> m ()
63+printState st = liftIO $ do
64+ let msv = streamVar st
65+ case msv of
66+ Just sv -> dumpSVar sv >>= putStrLn
67+ Nothing -> putStrLn "No SVar"
7168
7269-- | Pull a stream from an SVar.
7370{-# NOINLINE fromStreamVar #-}
@@ -81,16 +78,12 @@ fromStreamVar sv = Stream $ \st stp sng yld -> do
8178
8279 where
8380
84− allDone stp =
85−#ifdef DIAGNOSTICS
86− do
81+ allDone stp = do
82+ when (svarInspectMode sv) $ do
8783 t <- liftIO $ getTime Monotonic
8884 liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
89−#ifdef DIAGNOSTICS_VERBOSE
9085 liftIO $ printSVar sv "SVar Done"
91−#endif
92−#endif
93− stp
86+ stp
9487
9588 {-# INLINE processEvents #-}
9689 processEvents [] = Stream $ \st stp sng yld -> do
@@ -125,12 +118,15 @@ fromSVar sv =
125118 unStream (fromStreamVar sv{svarRef = Just ref}) st stp sng yld
126119 where
127120
128− hook =
129−#ifdef DIAGNOSTICS_VERBOSE
130− do
131− printSVar sv "SVar Garbage Collected"
132−#endif
121+ hook = do
122+ when (svarInspectMode sv) $ do
123+ r <- liftIO $ readIORef (svarStopTime (svarStats sv))
124+ when (isNothing r) $
125+ printSVar sv "SVar Garbage Collected"
133126 cleanupSVar sv
127+ -- If there are any SVars referenced by this SVar a GC will prompt
128+ -- them to be cleaned up quickly.
129+ when (svarInspectMode sv) performMajorGC
134130
135131-- | Write a stream to an 'SVar' in a non-blocking manner. The stream can then
136132-- be read back from the SVar using 'fromSVar'.
@@ -215,6 +211,8 @@ rate r m = fromStream $ Stream $ \st stp sng yld ->
215211 error "rate: Minimum rate cannot be greater than maximum rate."
216212 _ -> unStream (toStream m) (setStreamRate r st) stp sng yld
217213
214+-- XXX implement for serial streams as well, as a simple delay
215+
218216{-
219217{-# RULES "rate serial" rate = yieldRateSerial #-}
220218yieldRateSerial :: Double -> SerialT m a -> SerialT m a
@@ -304,3 +302,8 @@ maxYields n m = fromStream $ Stream $ \st stp sng yld ->
304302{-# RULES "maxYields serial" maxYields = maxYieldsSerial #-}
305303maxYieldsSerial :: Maybe Int64 -> SerialT m a -> SerialT m a
306304maxYieldsSerial _ = id
305+
306+-- | Print debug information about an SVar when the stream ends
307+inspectMode :: IsStream t => t m a -> t m a
308+inspectMode m = fromStream $ Stream $ \st stp sng yld -> do
309+ unStream (toStream m) (setInspectMode st) stp sng yld
stack.yaml+1−3
@@ -8,9 +8,7 @@ extra-deps:
88 - Chart-1.9
99 - Chart-diagrams-1.9
1010 - SVGFonts-1.6.0.3
11−
12− - git: https://github.com/composewell/bench-show
13− commit: 38cb1b547d1a7ac62de09d59e28f4a1f71dba31b
11+ - bench-show-0.2.0
1412
1513flags: {}
1614extra-package-dbs: []
streamly.cabal+1−9
@@ -87,11 +87,6 @@ source-repository head
8787 type: git
8888 location: https://github.com/composewell/streamly
8989
90−flag diag
91− description: Diagnostics build
92− manual: True
93− default: False
94−
9590flag dev
9691 description: Development build
9792 manual: True
@@ -138,6 +133,7 @@ library
138133 , Streamly.Time
139134 , Streamly
140135 , Streamly.Tutorial
136+ , Streamly.Internal
141137
142138 default-language: Haskell2010
143139 ghc-options: -Wall
@@ -148,11 +144,7 @@ library
148144 if flag(no-fusion)
149145 cpp-options: -DDISABLE_FUSION
150146
151− if flag(diag)
152− cpp-options: -DDIAGNOSTICS
153−
154147 if flag(dev)
155− cpp-options: -DDIAGNOSTICS
156148 ghc-options: -Wmissed-specialisations
157149 -Wall-missed-specialisations
158150 -fno-ignore-asserts