Skip to content

Commit 0af0a16

Browse files
authored
Merge pull request #6560 from IntersectMBO/russoul/recon-grep
cardano-recon-framework: Add cardano-recon-grep and ContinuousFormula (v1.2.0)
2 parents f7a072d + 5d6983d commit 0af0a16

15 files changed

Lines changed: 505 additions & 137 deletions

File tree

bench/cardano-recon-framework/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Revision history for cardano-trace-ltl
22

3+
## 1.2.0 -- May 2026
4+
5+
* Add `ContinuousFormula` — the temporal-operator-free fragment of `Formula`, with `retract` for
6+
converting a `Formula` and `eval` for deciding it against a single event.
7+
* Add `cardano-recon-grep` executable — filters a JSON array of `TraceMessage`s by a list of
8+
continuous formulas, emitting only the messages that satisfy all of them.
9+
* Add `--grep` flag to `cardano-recon` — on a negative formula outcome prints only the JSON array
10+
of relevant events to stdout (bypassing trace-dispatcher); prints nothing on a positive outcome.
11+
* Replace the raw context printout with a `ContextDump` trace message at `Debug` severity.
12+
313
## 1.1.1 -- April 2026
414
* Support atoms that refer to property keys at arbitrary depth
515
* Replace the `crash-on-missing-key` build flag with a runtime CLI option `--on-missing-key <crash|bottom>` (default: `bottom`)

bench/cardano-recon-framework/README.md

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Cardano Re(altime) Con(formance) Framework
22

3-
## What it does
3+
## Applications
4+
5+
### `cardano-recon` — LTL conformance checker
46

57
The application CLI takes as input:
68
- A yaml list of strings, where each string is a parseable Linear Temporal Logic formula.
@@ -14,14 +16,18 @@ The application CLI takes as input:
1416
The application traverses the events from the given log files and checks if each given formula is satisfied by them.
1517
If negative, reports as such and lists the events that have been relevant to the formula.
1618

17-
## CLI Syntax
19+
Pass `--grep` for machine-readable output: on a negative outcome only the JSON array of relevant events is written
20+
to stdout (bypassing trace-dispatcher), and nothing is printed on a positive outcome. This makes it straightforward
21+
to pipe results directly into `cardano-recon-grep`.
22+
23+
#### CLI Syntax
1824

1925
```
2026
Usage: cardano-recon FILE --mode <offline|online> --duration INT FILES
2127
[--retention INT] [--trace-dispatcher-cfg FILE]
2228
[--context FILE] [--dump-metrics BOOL] [--seek-to-end BOOL]
2329
[--timeunit <hour|minute|second|millisecond|microsecond>]
24-
[--on-missing-key <crash|bottom>]
30+
[--on-missing-key <crash|bottom>] [--grep]
2531
2632
Check formula satisfiability against a log of trace messages
2733
@@ -38,6 +44,44 @@ Available options:
3844
(default: True)
3945
--timeunit <hour|minute|second|millisecond|microsecond>
4046
timeunit (default: second)
47+
--on-missing-key <crash|bottom>
48+
behaviour when a formula atom references a missing
49+
event property key (default: bottom)
50+
--grep on formula violation print only the JSON array of
51+
relevant events; print nothing on satisfaction
52+
-h,--help Show this help text
53+
```
54+
55+
### `cardano-recon-grep` — Global Realisation Print
56+
57+
A stateless utility that filters a JSON array of trace messages by interpreting a list of
58+
_continuous_ (temporal-operator-free) formulas against each message individually.
59+
Only messages that satisfy **all** given formulas are written to stdout as a pretty-printed
60+
JSON array.
61+
62+
Inputs:
63+
- A YAML list of continuous formula strings (same syntax as `cardano-recon`, but temporal
64+
operators such as ``, ``, ``, and `|` are not permitted).
65+
For further details about the formula language, consult the [language overview](docs/formula-languages.txt).
66+
- A JSON array of `TraceMessage` objects, read from a file (`--traces FILE`) or from stdin
67+
if `--traces` is omitted (e.g. piped from `cardano-recon` on a negative formula outcome).
68+
- An optional context variables YAML file for variable substitution in formulas.
69+
- An `--on-missing-key` policy (default: `bottom`) controlling behaviour when a formula
70+
references a property absent from a message.
71+
72+
#### CLI Syntax
73+
74+
```
75+
Usage: cardano-recon-grep --formulas FILE [--traces FILE]
76+
[--context FILE]
77+
[--on-missing-key <crash|bottom>]
78+
79+
Print log events that realise all given continuous formulas (Global Realisation Print)
80+
81+
Available options:
82+
--formulas FILE YAML file with a list of ContinuousFormulas
83+
--traces FILE JSON array of TraceMessages to filter (default: stdin)
84+
--context FILE context variables YAML file
4185
--on-missing-key <crash|bottom>
4286
behaviour when a formula atom references a missing
4387
event property key (default: bottom)

bench/cardano-recon-framework/app/Cardano/ReCon.hs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import qualified Data.Map as Map
3434
import Data.Maybe (fromMaybe, listToMaybe)
3535
import Data.Text (Text)
3636
import qualified Data.Text as Text
37+
import qualified Data.Text.IO as TIO
3738
import Data.Traversable (for)
3839
import GHC.IO.Encoding (setLocaleEncoding, utf8)
3940
import Network.HostName (HostName)
@@ -45,18 +46,26 @@ import qualified System.Metrics as EKG
4546
import Streaming
4647

4748

48-
check :: OnMissingKey -> Word -> Trace IO App.TraceMessage -> Formula TemporalEvent Text -> [TemporalEvent] -> IO ()
49-
check omk idx {- Formula index -} tr phi events =
49+
check :: OnMissingKey -> Bool -> Word -> Trace IO App.TraceMessage -> Formula TemporalEvent Text -> [TemporalEvent] -> IO ()
50+
check omk greppable idx {- Formula index -} tr phi events =
5051
let result = satisfies omk phi events in
51-
traceWith tr $ formulaOutcome phi result idx
52-
53-
checkS' :: OnMissingKey -> Bool -> Word -> Trace IO App.TraceMessage -> Formula TemporalEvent Text -> Stream (Of TemporalEvent) IO () -> IO ()
54-
checkS' omk enableProgressDumps idx {- Formula index -} tr phi events = do
52+
if greppable
53+
then case result of
54+
Satisfied -> pure ()
55+
Unsatisfied rel -> TIO.putStrLn (App.prettyRelevanceArray rel)
56+
else traceWith tr $ formulaOutcome phi result idx
57+
58+
checkS' :: OnMissingKey -> Bool -> Bool -> Word -> Trace IO App.TraceMessage -> Formula TemporalEvent Text -> Stream (Of TemporalEvent) IO () -> IO ()
59+
checkS' omk greppable enableProgressDumps idx {- Formula index -} tr phi events = do
5560
let initial = SatisfyMetrics 0 phi 0
5661
metrics <- newIORef initial
5762
withAsync (when enableProgressDumps $ runDisplayProgressDump initial metrics) $ \counterDisplayThread -> do
5863
r <- satisfiesS omk phi events metrics
59-
traceWith tr $ formulaOutcome phi r idx
64+
if greppable
65+
then case r of
66+
Satisfied -> pure ()
67+
Unsatisfied rel -> TIO.putStrLn (App.prettyRelevanceArray rel)
68+
else traceWith tr $ formulaOutcome phi r idx
6069
cancel counterDisplayThread
6170
where
6271
runDisplayProgressDump :: SatisfyMetrics TemporalEvent Text -> IORef (SatisfyMetrics TemporalEvent Text) -> IO ()
@@ -69,6 +78,7 @@ checkS' omk enableProgressDumps idx {- Formula index -} tr phi events = do
6978
runDisplayProgressDump next counter
7079

7180
checkOnline :: OnMissingKey
81+
-> Bool
7282
-> Bool
7383
-> Trace IO App.TraceMessage
7484
-> TemporalEventDurationMicrosec
@@ -78,23 +88,24 @@ checkOnline :: OnMissingKey
7888
-> [FilePath]
7989
-> [Formula TemporalEvent Text]
8090
-> IO ()
81-
checkOnline omk enableProgressDumps tr eventDuration retentionMs failureMode ingestMode files phis = do
91+
checkOnline omk greppable enableProgressDumps tr eventDuration retentionMs failureMode ingestMode files phis = do
8292
ing <- mkIngestor (fromIntegral retentionMs)
8393
for_ files (ingestFileThreaded ing failureMode ingestMode)
8494
forConcurrently_ (zip [0..] phis) $ \(idx, phi) -> mkIngestorReader ing >>= \reader -> forever $ do
8595
traceWith tr $ FormulaStartCheck phi idx
86-
checkS' omk enableProgressDumps idx tr phi (readS reader eventDuration)
96+
checkS' omk greppable enableProgressDumps idx tr phi (readS reader eventDuration)
8797

8898
checkOffline :: OnMissingKey
99+
-> Bool
89100
-> Trace IO App.TraceMessage
90101
-> TemporalEventDurationMicrosec
91102
-> FilePath
92103
-> [Formula TemporalEvent Text]
93104
-> IO ()
94-
checkOffline omk tr eventDuration file phis = do
105+
checkOffline omk greppable tr eventDuration file phis = do
95106
events <- read file eventDuration
96107
forConcurrently_ (zip [0..] phis) $ \(idx, phi) ->
97-
check omk idx tr phi events
108+
check omk greppable idx tr phi events
98109
threadDelay 200_000 -- Give the tracer a grace period to output the logs to whatever backend
99110

100111
setupTraceDispatcher :: Maybe FilePath -> IO (Trace IO App.TraceMessage)
@@ -139,8 +150,6 @@ main = do
139150
setLocaleEncoding utf8
140151
options <- execParser opts
141152
ctx <- Map.toList . fromMaybe Map.empty <$> for options.context (readPropValues >=> dieOnYamlException)
142-
putStrLn "Context:"
143-
print ctx
144153
formulas <- readFormulas options.formulas (Context { interpDomain = ctx, varKinds = Map.empty }) Parser.name >>= dieOnYamlException
145154
for_ (fmap (\phi -> (phi, checkFormula mempty phi)) formulas) $ \case
146155
(phi, e : es) -> die $
@@ -152,15 +161,17 @@ main = do
152161
(_, []) -> pure ()
153162
let formulas' = fmap (interpTimeunit (\u -> timeunitToMicrosecond options.timeunit u `div` fromIntegral options.duration)) formulas
154163
tr <- setupTraceDispatcher options.traceDispatcherCfg
164+
traceWith tr $ ContextDump (map (second showT) ctx)
155165
case options.mode of
156166
Offline -> do
157167
file <- case options.traces of
158168
[x] -> pure x
159169
_ -> die "Only exactly one trace file is supported in 'offline' mode"
160-
checkOffline options.onMissingKey tr options.duration file formulas'
170+
checkOffline options.onMissingKey options.greppable tr options.duration file formulas'
161171
Online -> do
162172
checkOnline
163173
options.onMissingKey
174+
options.greppable
164175
options.enableProgressDumps
165176
tr
166177
options.duration

bench/cardano-recon-framework/app/Cardano/ReCon/Cli.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ parseOnMissingKey = option readOnMissingKey $
134134
<> value BottomOnMissingKey
135135
<> help "behaviour when a formula atom references a missing event property key"
136136

137+
parseGreppable :: Parser Bool
138+
parseGreppable = switch $
139+
long "grep"
140+
<> help "on formula violation print only the JSON array of relevant events; print nothing on satisfaction"
141+
137142
data CliOptions = CliOptions
138143
{ formulas :: FilePath
139144
, mode :: Mode
@@ -146,6 +151,7 @@ data CliOptions = CliOptions
146151
, enableSeekToEnd :: Bool
147152
, timeunit :: Timeunit
148153
, onMissingKey :: OnMissingKey
154+
, greppable :: Bool
149155
}
150156

151157
parseCliOptions :: Parser CliOptions
@@ -161,6 +167,7 @@ parseCliOptions = CliOptions
161167
<*> parseSeekToEnd
162168
<*> parseTimeunit
163169
<*> parseOnMissingKey
170+
<*> parseGreppable
164171

165172
opts :: ParserInfo CliOptions
166173
opts = info (parseCliOptions <**> helper)

bench/cardano-recon-framework/app/Cardano/ReCon/Common.hs

Lines changed: 0 additions & 16 deletions
This file was deleted.

bench/cardano-recon-framework/app/Cardano/ReCon/TraceMessage.hs

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@ module Cardano.ReCon.TraceMessage where
55
import Cardano.Logging
66
import Cardano.Logging.Prometheus.TCPServer (TracePrometheusSimple (..))
77
import qualified Cardano.Logging.Types.TraceMessage as Envelop
8-
import Cardano.ReCon.Common (extractJsonProps)
98
import Cardano.ReCon.LTL.Formula (Formula, Relevance)
109
import qualified Cardano.ReCon.LTL.Formula.Prec as Prec
1110
import Cardano.ReCon.LTL.Formula.Pretty (prettyFormula)
1211
import Cardano.ReCon.LTL.Satisfy (SatisfactionResult (..))
1312
import Cardano.ReCon.Trace.Feed (TemporalEvent (..))
1413

15-
import Data.Aeson (Value (..), (.=))
14+
import Data.Aeson ((.=))
1615
import Data.Aeson.Encode.Pretty
1716
import Data.List (find)
18-
import qualified Data.Map as Map
1917
import qualified Data.Set as Set
2018
import Data.Text (Text)
2119
import qualified Data.Text as Text
@@ -44,13 +42,14 @@ data TraceMessage = FormulaStartCheck {
4442
| FormulaNegativeOutcome {
4543
formula :: Formula TemporalEvent Text,
4644
relevance :: Relevance TemporalEvent Text,
47-
index :: Word
45+
index :: Word
4846
}
47+
| ContextDump { context :: [(Text, Text)] }
4948

5049
-- | Smart constructor.
5150
formulaOutcome :: Formula TemporalEvent Text -> SatisfactionResult TemporalEvent Text -> Word -> TraceMessage
52-
formulaOutcome formula Satisfied idx = FormulaPositiveOutcome formula idx
53-
formulaOutcome formula (Unsatisfied rel) idx = FormulaNegativeOutcome formula rel idx
51+
formulaOutcome formula Satisfied idx = FormulaPositiveOutcome { formula, index = idx }
52+
formulaOutcome formula (Unsatisfied rel) idx = FormulaNegativeOutcome { formula, relevance = rel, index = idx }
5453

5554
green :: Text -> Text
5655
green text = "\x001b[32m" <> text <> "\x001b[0m"
@@ -59,51 +58,49 @@ red :: Text -> Text
5958
red text = "\x001b[31m" <> text <> "\x001b[0m"
6059

6160
prettyTraceMessage :: Envelop.TraceMessage -> Text
62-
prettyTraceMessage Envelop.TraceMessage{..} =
63-
toStrict $ toLazyText $ encodePrettyToTextBuilder $
64-
Map.insert "at" (String (showT tmsgAt)) $
65-
Map.insert "namespace" (String tmsgNS) $
66-
Map.insert "host" (String tmsgHost) $
67-
Map.insert "thread" (String tmsgThread) $
68-
extractJsonProps tmsgData
61+
prettyTraceMessage = toStrict . toLazyText . encodePrettyToTextBuilder
6962

7063
prettyTemporalEvent :: TemporalEvent -> Text -> Text
7164
prettyTemporalEvent (TemporalEvent _ msgs) ns =
7265
maybe ("<<Unexpected namespace " <> ns <> ">>") prettyTraceMessage (find (\ x -> x.tmsgNS == ns) msgs)
7366

67+
prettyRelevanceArray :: Relevance TemporalEvent Text -> Text
68+
prettyRelevanceArray rel =
69+
Text.unlines $ "[" : fmap (uncurry prettyTemporalEvent) (Set.toList rel) ++ ["]"]
70+
7471
prettySatisfactionResult :: Formula TemporalEvent Text -> SatisfactionResult TemporalEvent Text -> Text
7572
prettySatisfactionResult initial Satisfied = prettyFormula initial Prec.Universe <> " " <> green "(✔)"
7673
prettySatisfactionResult initial (Unsatisfied rel) =
7774
prettyFormula initial Prec.Universe <> red " (✗)" <> "\n"
78-
<> Text.intercalate
79-
"\n----------------------------------------------\n"
80-
(fmap (uncurry prettyTemporalEvent) (Set.toList rel))
75+
<> prettyRelevanceArray rel
8176

8277
instance LogFormatting TraceMessage where
8378
forMachine _ FormulaStartCheck{..} = mconcat
8479
[
85-
"formula" .= String (prettyFormula formula Prec.Universe),
80+
"formula" .= prettyFormula formula Prec.Universe,
8681
"index" .= index
8782
]
8883
forMachine _ FormulaProgressDump{..} = mconcat
8984
[
90-
"events_per_second" .= Number (fromIntegral eventsPerSecond),
91-
"catch_up_ratio" .= Number (realToFrac catchupRatio),
85+
"eventsPerSecond" .= (fromIntegral eventsPerSecond :: Int),
86+
"catchUpRatio" .= (realToFrac catchupRatio :: Double),
9287
"index" .= index
9388
]
9489
forMachine _ FormulaPositiveOutcome{..} = mconcat
9590
[
96-
"formula" .= String (prettyFormula formula Prec.Universe),
91+
"formula" .= prettyFormula formula Prec.Universe,
9792
"index" .= index
9893
]
9994
forMachine _ FormulaNegativeOutcome{..} = mconcat
10095
[
101-
"formula" .= String (prettyFormula formula Prec.Universe)
96+
"formula" .= prettyFormula formula Prec.Universe
10297
,
103-
"relevance" .= String (showT relevance)
98+
"relevance" .= showT relevance
10499
,
105100
"index" .= index
106101
]
102+
forMachine _ ContextDump{..} = mconcat
103+
[ "context" .= context ]
107104

108105
forHuman FormulaStartCheck{..} =
109106
"Starting satisfiability check on formula #" <> showT index <> ": " <> prettyFormula formula Prec.Universe
@@ -119,28 +116,35 @@ instance LogFormatting TraceMessage where
119116
prettySatisfactionResult formula Satisfied
120117
forHuman FormulaNegativeOutcome{..} =
121118
prettySatisfactionResult formula (Unsatisfied relevance)
119+
forHuman ContextDump{..} =
120+
Text.unlines $ "Context:" : map (\(k, v) -> " " <> k <> " = " <> v) context
122121

123122
asMetrics FormulaStartCheck{} = []
124123
asMetrics (FormulaProgressDump {catchupRatio, index}) = [DoubleM ("catchup_ratio_" <> showT index) catchupRatio]
125124
asMetrics FormulaPositiveOutcome{} = []
126125
asMetrics FormulaNegativeOutcome{} = []
126+
asMetrics ContextDump{} = []
127127

128128

129129
instance MetaTrace TraceMessage where
130130
allNamespaces =
131131
[
132+
Namespace [] ["ContextDump"]
133+
,
132134
Namespace [] ["FormulaStartCheck"]
133135
,
134136
Namespace [] ["FormulaProgressDump"]
135137
,
136138
Namespace [] ["FormulaOutcome"]
137139
]
138140

141+
namespaceFor ContextDump{} = Namespace [] ["ContextDump"]
139142
namespaceFor FormulaStartCheck{} = Namespace [] ["FormulaStartCheck"]
140143
namespaceFor FormulaProgressDump{} = Namespace [] ["FormulaProgressDump"]
141144
namespaceFor FormulaPositiveOutcome{} = Namespace [] ["FormulaPositiveOutcome"]
142145
namespaceFor FormulaNegativeOutcome{} = Namespace [] ["FormulaNegativeOutcome"]
143146

147+
severityFor (Namespace [] ["ContextDump"]) _ = Just Debug
144148
severityFor (Namespace [] ["FormulaStartCheck"]) _ = Just Info
145149
severityFor (Namespace [] ["FormulaProgressDump"]) _ = Just Debug
146150
severityFor (Namespace [] ["FormulaPositiveOutcome"]) _ = Just Info
@@ -149,6 +153,8 @@ instance MetaTrace TraceMessage where
149153

150154
detailsFor _ _ = Just DNormal
151155

156+
documentFor (Namespace [] ["ContextDump"]) =
157+
Just "Dump of the context variables supplied to the formula parser."
152158
documentFor (Namespace [] ["FormulaStartCheck"]) =
153159
Just "Formula satisfiability check has started."
154160
documentFor (Namespace [] ["FormulaProgressDump"]) =

0 commit comments

Comments
 (0)