Skip to content

Commit 5b62976

Browse files
committed
cardano-tracer: Add functionality to run cardano-tracer as a library, with shut-down functionality and internal/user messaging.
Signed-off-by: Baldur Blöndal <baldur.blondal@iohk.io>
1 parent 0614f23 commit 5b62976

File tree

12 files changed

+374
-100
lines changed

12 files changed

+374
-100
lines changed

cardano-tracer/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# ChangeLog
22

3+
## NEXT
4+
* Cardano-tracer library functionality, allows shutting down and sending signals to running
5+
instances through channels.
6+
37
## 0.3.5 (October, 2025)
48
* Updated to `ekg-forward-1.0`, `ouroboros-network-0.22.3`, `ouroboros-network-api-0.16` and `ouroboros-network-0.22.3`.
59
* Updated metric names
Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
import Cardano.Tracer.CLI (TracerParams, parseTracerParams)
1+
{-# LANGUAGE OverloadedRecordDot #-}
2+
3+
import Cardano.Tracer.CLI (TracerParams(..), parseTracerParams)
4+
import Cardano.Tracer.MetaTrace
25
import Cardano.Tracer.Run (runCardanoTracer)
36

7+
import Data.Functor (void)
48
import Data.Version (showVersion)
59
import Options.Applicative
610

711
import Paths_cardano_tracer (version)
812

913
main :: IO ()
10-
main =
11-
runCardanoTracer =<< customExecParser (prefs showHelpOnEmpty) tracerInfo
14+
main = void do
15+
tracerParams :: TracerParams
16+
<- customExecParser (prefs showHelpOnEmpty) tracerInfo
17+
trace :: Trace IO TracerTrace <-
18+
-- Default `Nothing' severity filter to Info.
19+
mkTracerTracer $ SeverityF (tracerParams.logSeverity <|> Just Info)
20+
runCardanoTracer trace tracerParams
1221

1322
tracerInfo :: ParserInfo TracerParams
1423
tracerInfo = info
@@ -21,7 +30,9 @@ tracerInfo = info
2130

2231
versionOption :: Parser (a -> a)
2332
versionOption = infoOption
24-
(showVersion version)
25-
(long "version" <>
26-
short 'v' <>
27-
help "Show version")
33+
do showVersion version
34+
do mconcat
35+
[ long "version"
36+
, short 'v'
37+
, help "Show version"
38+
]

cardano-tracer/bench/cardano-tracer-bench.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Control.Concurrent.Extra (newLock)
1919
#if RTVIEW
2020
import Control.Concurrent.STM.TVar (newTVarIO)
2121
#endif
22+
import Control.Concurrent.Chan.Unagi (newChan)
2223
import Control.DeepSeq
2324
import qualified Data.List.NonEmpty as NE
2425
import Data.Time.Clock (UTCTime, getCurrentTime)
@@ -63,6 +64,8 @@ main = do
6364

6465
tracer <- mkTracerTracer $ SeverityF $ Just Warning
6566

67+
(inChan, _outChan) <- newChan
68+
6669
let tracerEnv :: TracerConfig -> HandleRegistry -> TracerEnv
6770
tracerEnv config handleRegistry = TracerEnv
6871
{ teConfig = config
@@ -74,6 +77,7 @@ main = do
7477
, teDPRequestors = dpRequestors
7578
, teProtocolsBrake = protocolsBrake
7679
, teTracer = tracer
80+
, teInChan = inChan
7781
, teReforwardTraceObjects = \_-> pure ()
7882
, teRegistry = handleRegistry
7983
, teStateDir = Nothing

cardano-tracer/cardano-tracer.cabal

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ library
200200
, trace-dispatcher ^>= 2.10.0
201201
, trace-forward ^>= 2.3.0
202202
, trace-resources ^>= 0.2.4
203+
, unagi-chan
203204
, wai ^>= 3.2
204205
, warp ^>= 3.4
205206
, yaml
@@ -294,6 +295,7 @@ library demo-acceptor-lib
294295
exposed-modules: Cardano.Tracer.Test.Acceptor
295296

296297
build-depends: bytestring
298+
, QuickCheck
297299
, cardano-tracer
298300
, containers
299301
, extra
@@ -306,9 +308,9 @@ library demo-acceptor-lib
306308
, text
307309
, trace-dispatcher
308310
, trace-forward
311+
, unagi-chan
309312
, vector
310313
, vector-algorithms
311-
, QuickCheck
312314

313315
executable demo-acceptor
314316
import: project-config
@@ -452,12 +454,13 @@ benchmark cardano-tracer-bench
452454
build-depends: stm <2.5.2 || >=2.5.3
453455
build-depends: cardano-tracer
454456
, criterion
455-
, directory
456457
, deepseq
458+
, directory
457459
, extra
458460
, filepath
459461
, time
460462
, trace-dispatcher
463+
, unagi-chan
461464

462465
ghc-options: -threaded
463466
-rtsopts

cardano-tracer/src/Cardano/Tracer/Acceptors/Run.hs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Cardano.Tracer.Utils
1414
import Cardano.Logging.Types (TraceObject)
1515
import qualified Cardano.Logging.Types as Net
1616

17+
import Control.Concurrent.Chan.Unagi (dupChan)
1718
import Control.Concurrent.Async (forConcurrently_)
1819
import "contra-tracer" Control.Tracer (Tracer, contramap, nullTracer, stdoutTracer)
1920
import qualified Data.List.NonEmpty as NE
@@ -33,20 +34,28 @@ import qualified Trace.Forward.Protocol.TraceObject.Type as TOF
3334
-- 1. Server mode, when the tracer accepts connections from any number of nodes.
3435
-- 2. Client mode, when the tracer initiates connections to specified number of nodes.
3536
runAcceptors :: TracerEnv -> TracerEnvRTView -> IO ()
36-
runAcceptors tracerEnv@TracerEnv{teTracer} tracerEnvRTView = do
37+
runAcceptors tracerEnv@TracerEnv{teTracer, teInChan = inChan} tracerEnvRTView = do
3738
traceWith teTracer $ TracerStartedAcceptors network
3839
case network of
39-
AcceptAt howToConnect ->
40+
AcceptAt howToConnect -> let
4041
-- Run one server that accepts connections from the nodes.
41-
runInLoop
42-
(runAcceptorsServer tracerEnv tracerEnvRTView howToConnect $ acceptorsConfigs (Net.howToConnectString howToConnect))
43-
verbosity howToConnect initialPauseInSec
44-
ConnectTo localSocks ->
42+
43+
action :: IO ()
44+
action = do
45+
dieOnShutdown =<< dupChan inChan
46+
runAcceptorsServer tracerEnv tracerEnvRTView howToConnect $ acceptorsConfigs (Net.howToConnectString howToConnect)
47+
48+
in runInLoop action verbosity howToConnect initialPauseInSec
49+
ConnectTo localSocks -> do
4550
-- Run N clients that initiate connections to the nodes.
46-
forConcurrently_ (NE.nub localSocks) \howToConnect ->
47-
runInLoop
48-
(runAcceptorsClient tracerEnv tracerEnvRTView howToConnect $ acceptorsConfigs (Net.howToConnectString howToConnect))
49-
verbosity howToConnect initialPauseInSec
51+
forConcurrently_ (NE.nub localSocks) \howToConnect -> let
52+
53+
action :: IO ()
54+
action = runAcceptorsClient tracerEnv tracerEnvRTView howToConnect $ acceptorsConfigs (Net.howToConnectString howToConnect)
55+
56+
in do
57+
dieOnShutdown =<< dupChan inChan
58+
runInLoop action verbosity howToConnect initialPauseInSec
5059
where
5160
TracerConfig{network, ekgRequestFreq, verbosity, ekgRequestFull} = teConfig tracerEnv
5261
ekgUseFullRequests = fromMaybe False ekgRequestFull

cardano-tracer/src/Cardano/Tracer/Environment.hs

Lines changed: 194 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
1+
{-# LANGUAGE ExplicitNamespaces #-}
12
{-# LANGUAGE CPP #-}
3+
{-# LANGUAGE GADTs #-}
4+
{-# LANGUAGE LambdaCase #-}
25

36
module Cardano.Tracer.Environment
47
( TracerEnv (..)
58
, TracerEnvRTView (..)
9+
, RawMessage (..)
10+
, InternalMessage (..)
11+
, Tag (..)
12+
, CardanoTracerMessage
13+
, onRawMessage
14+
, onInternal
15+
, onUser
16+
, blockUntilShutdown
17+
, dieOnShutdown
18+
, forever'tilShutdown
19+
20+
, type MessageHandler
21+
, handleMessage
22+
, handleMessages
23+
, handleMessageWithShutdown
24+
, handleMessagesWithShutdown
25+
, handleShutdown
26+
, handleInternal
27+
, handleUser
628
) where
729

830
import Cardano.Logging.Types
31+
import Cardano.Logging.Resources.Types (ResourceStats)
932
import Cardano.Tracer.Configuration
1033
#if RTVIEW
1134
import Cardano.Tracer.Handlers.Notifications.Types
@@ -16,10 +39,14 @@ import Cardano.Tracer.Handlers.State.TraceObjects
1639
import Cardano.Tracer.MetaTrace
1740
import Cardano.Tracer.Types
1841

42+
import Control.Concurrent (myThreadId)
43+
import Control.Exception (AsyncException(ThreadKilled), throwTo)
44+
import Control.Concurrent.Chan.Unagi (InChan, OutChan, readChan, tryReadChan, tryRead)
1945
import Control.Concurrent.Extra (Lock)
46+
import Data.Foldable (traverse_)
2047
import Data.Text (Text)
2148
import Data.Text.Lazy.Builder (Builder)
22-
49+
import Data.Kind (Type)
2350

2451
-- | Environment for all functions.
2552
data TracerEnv = TracerEnv
@@ -36,6 +63,7 @@ data TracerEnv = TracerEnv
3663
, teRegistry :: !HandleRegistry
3764
, teStateDir :: !(Maybe FilePath)
3865
, teMetricsHelp :: ![(Text, Builder)]
66+
, teInChan :: !(InChan (CardanoTracerMessage ()))
3967
}
4068

4169
#if RTVIEW
@@ -51,3 +79,168 @@ data TracerEnvRTView = TracerEnvRTView
5179
#else
5280
data TracerEnvRTView = TracerEnvRTView
5381
#endif
82+
83+
type CardanoTracerMessage userMsg = RawMessage InternalMessage userMsg
84+
85+
type RawMessage :: Type -> Type -> Type
86+
data RawMessage internal user
87+
= Shutdown
88+
| InternalMessage internal
89+
| UserMessage user
90+
91+
type InternalMessage :: Type
92+
data InternalMessage where
93+
HandleInternalMessage :: Tag ex -> (ex -> IO ()) -> InternalMessage
94+
95+
type Tag :: Type -> Type
96+
data Tag a where
97+
ResourceStatsTag :: Tag (ResourceStats, Trace IO TracerTrace)
98+
99+
-- | Polls the channel until a @Shutdown@ message is received.
100+
blockUntilShutdown :: OutChan (RawMessage internal user) -> IO ()
101+
blockUntilShutdown outChan = go where
102+
go :: IO ()
103+
go = readChan outChan >>= \case
104+
Shutdown -> pure ()
105+
_ -> go
106+
107+
-- | Serves a channel with a composable `MessageHandler'-function.
108+
--
109+
-- @
110+
-- onInternal = handleMessageWithShutdown . handleInternal
111+
-- onUser = handleMessageWithShutdown . handleUser
112+
-- dieOnShutdown = handleMessageWithShutdown mempty
113+
-- @
114+
--
115+
-- These handlers are composable with the function Monoid instance.
116+
--
117+
-- @
118+
-- handleMessage (handleInternal handle1 <> handleUser handle2)
119+
-- = handleMessages [handleInternal handle1, handleUser handle2]
120+
-- @
121+
--
122+
-- Where @handleMessage (a <> b <> c)@ is equivalent to @handleMessages [a, b, c]@.
123+
--
124+
-- Instantiations:
125+
--
126+
-- @
127+
-- handleMessage :: (RawMessage internal user -> IO ()) -> OutChan (RawMessage internal user) -> IO ()
128+
-- handleMessage :: MessageHandler internal user -> OutChan (RawMessage internal user) -> IO ()
129+
-- @
130+
handleMessage :: (chan -> IO ()) -> OutChan chan -> IO ()
131+
handleMessage handler outChan = do
132+
(element, _out) <- tryReadChan outChan
133+
tryRead element >>= traverse_ @Maybe handler
134+
135+
handleMessages :: [MessageHandler internal user] -> OutChan (RawMessage internal user) -> IO ()
136+
handleMessages = handleMessage . mconcat
137+
138+
handleMessageWithShutdown :: MessageHandler internal user -> OutChan (RawMessage internal user) -> IO ()
139+
handleMessageWithShutdown handler = handleMessage (handler <> handleShutdown)
140+
141+
handleMessagesWithShutdown :: [MessageHandler internal user] -> OutChan (RawMessage internal user) -> IO ()
142+
handleMessagesWithShutdown = handleMessageWithShutdown . mconcat
143+
144+
onRawMessage :: (internal -> IO ()) -> (user -> IO ()) -> OutChan (RawMessage internal user) -> IO ()
145+
onRawMessage internal user = handleMessagesWithShutdown
146+
[ handleInternal internal
147+
, handleUser user
148+
]
149+
150+
-- onInternal = (`onRawMessage` mempty)
151+
onInternal :: (internal -> IO ()) -> OutChan (RawMessage internal user) -> IO ()
152+
onInternal = handleMessageWithShutdown . handleInternal
153+
154+
-- onUser = (mempty `onRawMessage`)
155+
onUser :: (user -> IO ()) -> OutChan (RawMessage internal user) -> IO ()
156+
onUser = handleMessageWithShutdown . handleUser
157+
158+
-- dieOnShutdown = onRawMessage mempty mempty
159+
dieOnShutdown :: OutChan (RawMessage internal user) -> IO ()
160+
dieOnShutdown = handleMessagesWithShutdown []
161+
162+
forever'tilShutdown :: OutChan (RawMessage internal user) -> IO () -> IO ()
163+
forever'tilShutdown outChan action = do
164+
(element, _out) <- tryReadChan outChan
165+
tryRead element >>= \case
166+
Just Shutdown -> pure ()
167+
Just _ -> forever'tilShutdown outChan action
168+
Nothing -> action *> forever'tilShutdown outChan action
169+
170+
-- | Composable handlers, with a functional Monoidal instance,
171+
-- via 'Ap (RawMessage internal user ->) (IO ())':
172+
--
173+
-- @
174+
-- instance Semigroup (MessageHandler internal user) where
175+
-- (<>) = liftA2 (<>)
176+
-- instance Monoid (MessageHandler internal user) where
177+
-- mempty = pure mempty
178+
-- @
179+
--
180+
-- The handler functions are composed together, the incoming argument
181+
-- gets passed pointwise to each function.
182+
--
183+
-- @
184+
-- (handleShutdown <> handleInternal internal <> handleUser user) Shutdown
185+
-- = handleShutdown Shutdown <> handleInternal internal Shutdown <> handleUser user Shutdown
186+
-- = handleShutdown Shutdown <> mempty <> mempty
187+
-- = myThreadId >>= (`throwTo` ThreadKilled)
188+
--
189+
-- (handleShutdown <> handleInternal internal <> handleUser user) (InternalMessage message)
190+
-- = handleShutdown (InternalMessage message) <> handleInternal internal (InternalMessage message) <> handleUser user (InternalMessage message)
191+
-- = internal message
192+
-- @
193+
194+
type MessageHandler :: Type -> Type -> Type
195+
type MessageHandler internal user = RawMessage internal user -> IO ()
196+
197+
handleShutdown :: MessageHandler internal user
198+
handleShutdown = \case
199+
Shutdown -> myThreadId >>= (`throwTo` ThreadKilled)
200+
_ -> mempty
201+
202+
-- handleInternal :: Monoid m => (internal -> m) -> RawMessage internal user -> m
203+
handleInternal :: (internal -> IO ()) -> MessageHandler internal user
204+
handleInternal handler = \case
205+
InternalMessage internal -> handler internal
206+
_ -> mempty
207+
208+
-- handleUser :: Monoid m => (user -> m) -> RawMessage internal user -> m
209+
handleUser :: (user -> IO ()) -> MessageHandler internal user
210+
handleUser handler = \case
211+
UserMessage user -> handler user
212+
_ -> mempty
213+
214+
{- | UNSAFE shorthand
215+
216+
doing \case
217+
A -> res
218+
219+
is shorthand for
220+
221+
\case
222+
A -> res
223+
_ -> mempty
224+
225+
-- mapMaybe' = mapMaybe . partialBinding
226+
-- Just 10 >>= partialBinding \10 -> "10"
227+
partialBinding :: (a -> b) -> (a -> Maybe b)
228+
partialBinding f a = unsafePerformIO do
229+
try @PatternMatchFail (evaluate (f a)) >>= \case
230+
Left (PatternMatchFail err)
231+
| any (`isSuffixOf` err)
232+
[ ": Non-exhaustive patterns in lambda\n"
233+
, ": Non-exhaustive patterns in \\case\n"
234+
, ": Non-exhaustive patterns in \\cases\n"
235+
]
236+
-> pure @IO (Nothing)
237+
Left err ->
238+
throwIO err
239+
Right b ->
240+
pure @IO (Just b)
241+
242+
doing :: Monoid m => (a -> m) -> (a -> m)
243+
doing f a = case partialBinding f a of
244+
Nothing -> mempty
245+
Just b -> b
246+
-}

0 commit comments

Comments
 (0)