11{-# LANGUAGE NamedFieldPuns #-}
22{-# LANGUAGE OverloadedStrings #-}
3+ {-# LANGUAGE RecordWildCards #-}
34{-# LANGUAGE ScopedTypeVariables #-}
45
5- {-# OPTIONS_GHC -Wno-unused-imports -Wno-redundant-constraints -Wno- unused-top-binds #-}
6+ {-# OPTIONS_GHC -Wno-unused-imports -Wno-redundant-constraints -Wwarn= unused-top-binds -Wwarn=partial-fields -Wno-orphans #-}
67
78import Cardano.Api (ExceptT , SlotNo (.. ), runExceptT )
89
@@ -11,34 +12,44 @@ import Cardano.Analysis.Reducer
1112import Cardano.Analysis.Reducer.Util
1213import Cardano.Unlog.BackendDB
1314import Cardano.Unlog.BackendFile (readRunLogsBare )
14- import Cardano.Unlog.LogObject (LogObject (.. ), RunLogs (.. ), rlLogs )
15+ import Cardano.Unlog.LogObject (LogObject (.. ), RunLogs (.. ), hlLogs , rlLogs )
1516import Cardano.Unlog.LogObjectDB
1617import Cardano.Util hiding (toDouble )
1718
1819import Prelude hiding (log , seq )
1920
20- import Data.Bifunctor (first )
21+ import Data.Bifunctor
22+ import qualified Data.ByteString as B
2123import Data.Either
2224import Data.Function (on )
2325import Data.List (find , isSuffixOf )
24- import Data.Map.Strict as Map (restrictKeys )
26+ import Data.List.Extra (splitOn )
27+ import Data.Map.Strict as Map (restrictKeys , (!) )
28+ import Data.Maybe
2529import Data.Reducer
2630import qualified Data.Set as Set (fromList , null )
2731import Data.Word
28- import System.Environment (getArgs )
32+ import Options.Applicative as Opt
33+ import System.FilePath (splitDirectories , takeExtension )
2934
3035import Database.Sqlite.Easy hiding (Text )
3136import Graphics.EasyPlot
3237
3338
3439main :: IO ()
3540main = do
36- getArgs >>= \ case
37- db : _
38- | " .sqlite3" `isSuffixOf` db -> runDB $ fromString db
39- | " .json" `isSuffixOf` db -> runManifest db
40- | db == " testplot" -> void testPlot
41- _ -> putStrLn " please specify DB file or log manifest, or 'testplot'"
41+ cli <- parseCommandLine
42+ -- print cli
43+ case cli of
44+ CMDTestPlot -> void testPlot
45+ CMDProcess m -> do
46+ putStrLn $ " --> reading result blob from: " ++ show m
47+ CMDQuery {.. } -> case cInputs of
48+ [db] | takeExtension db == " .sqlite3"
49+ -> runDB $ fromString db
50+ inputs
51+ -> mapM (runManifest cNodes) (zip inputs [0 :: Int .. ]) >>= runPlot
52+ CMDTestPipe r -> testPipe r
4253
4354-- for testing the EasyPlot module
4455testPlot :: IO Bool
@@ -56,36 +67,70 @@ testPlot =
5667 , File2D [Title " from file" , Color Yellow ] [] " _plot1.dat" Nothing
5768 ]
5869
59- runManifest :: FilePath -> IO ()
60- runManifest logManifest = do
70+ testPipe :: Bool -> IO ()
71+ testPipe readMode
72+ | readMode = do
73+ inp <- B. getContents
74+ putStrLn $ " --> does stdin match test buffer: " ++ show (inp == testBuffer)
75+ | otherwise = B. putStr testBuffer
76+ where
77+ testBuffer = B. pack $ take 2048 $ cycle [0 .. 255 ]
78+
79+ runManifest :: [String ] -> (FilePath , Int ) -> IO (Graph2D Double Double )
80+ runManifest targetNodes (logManifest, ix) = do
6181 rls <- withTimingInfo " quick-query" $
6282 -- runOnRun (LoadLogObjectsWith selectMempoolTxs) logManifest [] -- ["node-2", "node-12", "node-22"]
63- -- runOnRun LoadHeapData logManifest [] -- ["node-2", "node-12", "node-22" ]
64- runOnRun LoadTimestamps logManifest [] -- ["node-2", "node-12", "node-22"]
83+ runOnRun LoadHeapData logManifest [targetNode ]
84+ -- runOnRun LoadTimestamps logManifest [] -- ["node-2", "node-12", "node-22"]
6585
6686 {-
6787 let
6888 perSlot :: RunLogs [ObjectsInSlot]
6989 perSlot = bySlotDomain `fmap` rls
7090
71- red = reduceRunLogs (TxsInMempoolPerSlot <-> Changes ((/=) `on` snd )) perSlot
91+ red = reduceRunLogs (TxsInMempoolPerSlot <-> changes )) perSlot
7292 -}
7393
7494 {-
95+ now <- getCurrentTime
7596 let
76- perSlot :: RunLogs [BySlot Word64]
77- perSlot = bySlot
78- (either (const Nothing) Just)
79- (fromLeft undefined)
80- False
81- `fmap` rls
97+ res = reduceRunLogs Silence{threshold = 1.2, startTime = now} rls
98+ mapM_ print (rlLogs res)
8299 -}
83100
84- now <- getCurrentTime
85101 let
86- res = reduceRunLogs Silence {threshold = 1.2 , startTime = now} rls
102+ target = snd $ hlLogs $ rlHostLogs rls Map. ! fromString targetNode
87103
88- mapM_ print (rlLogs res)
104+ bySlotValue :: [BySlot Word64 ]
105+ bySlotValue = bySlot (either (const Nothing ) Just ) (fromLeft undefined ) False target
106+
107+ res = reduce ResourceMeasurePerSlot bySlotValue
108+ bumps = reduce changes res
109+ gibibytes = map (second (\ w -> fromIntegral w / 1073741824 )) (toDouble bumps)
110+
111+ plotData = Data2D [Title targetTitle, Color targetColor, Style Steps ] [] gibibytes
112+ putStrLn $ " --> target node: " ++ targetNode
113+ mapM_ print bumps
114+ pure plotData
115+
116+ where
117+ targetNode = fromMaybe " node-10" $ listToMaybe targetNodes
118+ targetColor = cycle [Red , Blue , Green , Yellow , Magenta , Cyan ] !! ix
119+ targetTitle = case splitDirectories logManifest of
120+ " run" : runId : _ -> runId -- workbench use
121+ _ -> logManifest -- any other manual use
122+
123+ runPlot :: Plot a => a -> IO ()
124+ runPlot plotData =
125+ void $ plot' [preamble] term plotData
126+ where
127+ term = Terminal Qt " Heap size bumps" (Just (1440 , 960 ))
128+ preamble = Preamble
129+ [ " set xlabel \" Slot\" "
130+ , " set ylabel \" Heap size (GiB)\" "
131+ , " set format y \" %'.2f\" "
132+ , " set ytics nomirror"
133+ ]
89134
90135runOnRun :: forall l . LoadFromDB l => l -> FilePath -> [String ] -> IO (RunLogs [LoadResult l ])
91136runOnRun loadFromDB logManifest onlyHosts =
@@ -122,12 +167,9 @@ runDB dbName = do
122167 res2 = reduce changes res1
123168
124169 let
125- toDouble :: SlotNo -> Double
126- toDouble = fromIntegral . unSlotNo
127-
128- term = Terminal X11 " Txns in Mempool" (Just (1024 , 768 ))
129- points1 = map (first toDouble) res1
130- points2 = map (first toDouble) res2
170+ term = Terminal X11 " Txns in Mempool" (Just (1280 , 960 ))
171+ points1 = toDouble res1
172+ points2 = toDouble res2
131173
132174 void $ plot' [] term (Data2D [Title " txn count per slot" , Color Red , Style Linespoints ] [] points1)
133175 void $ plot' [] term (Data2D [Title " txn count changes" , Color Blue , Style Steps ] [] points2)
@@ -139,6 +181,9 @@ selectMempoolTxs =
139181 ]
140182
141183
184+ toDouble :: [(SlotNo , a )] -> [(Double , a )]
185+ toDouble = map (first (fromIntegral . unSlotNo))
186+
142187{-
143188 This should eventually be part of a QuickQuery typeclass. This class is defined by:
144189 - a query + (possibly parametrizable) filter, making use of the LoadFromDB typeclass
@@ -150,6 +195,8 @@ selectMempoolTxs =
150195
151196data LoadHeapData = LoadHeapData
152197
198+
199+ -- TODO: use timestamp to infer slot numbers during startup
153200instance LoadFromDB LoadHeapData where
154201 type instance LoadResult LoadHeapData = Either Word64 SlotNo
155202
@@ -177,3 +224,94 @@ instance LoadFromDB LoadTimestamps where
177224 loadConvert _ _ = \ case
178225 [at, slot] -> (fromSqlData at, fromSqlData slot)
179226 _ -> error " loadConvert(LoadTimestamps): expected 2 result columns"
227+
228+
229+ --
230+ -- command line parsing
231+ --
232+
233+ data ProcessMode
234+ = PassThru -- ^ read result blob from stdin
235+ | WriteBlob FilePath -- ^ read result blob from stdin and also write to file (to retain a local copy when blob is piped from remote)
236+ | ReadBlob FilePath -- ^ read result blob from file
237+
238+ instance Show ProcessMode where
239+ show PassThru = " <stdin>"
240+ show (WriteBlob f) = " <stdin> (will dump to: " ++ f ++ " )"
241+ show (ReadBlob f) = f
242+
243+ data Command
244+ = CMDTestPlot
245+ | CMDQuery
246+ { cQuery :: () -- ^ the query to run
247+ , cInputs :: [FilePath ] -- ^ log manifests of all runs to query, or a single .sqlite3 DB
248+ , cNodes :: [String ] -- ^ hosts to query (e.g. ["node-10", "node-12"]; empty: all hosts
249+ , cDumpResult :: Bool -- ^ dump result blob to stdout only; don't process
250+ }
251+ | CMDProcess
252+ { cProcessMode :: ProcessMode
253+ }
254+ | CMDTestPipe
255+ { cReadMode :: Bool
256+ }
257+ deriving Show
258+
259+ parseCommandLine :: IO Command
260+ parseCommandLine
261+ = post <$> Opt. customExecParser p opts
262+ where
263+ p = Opt. prefs Opt. showHelpOnEmpty
264+ opts = Opt. info parserCommandLine Opt. fullDesc
265+
266+ -- post processing: if run paths were given, extend to log manifest path accoring to workbench default
267+ post = \ case
268+ cmd@ CMDQuery {cInputs = ps} -> cmd {cInputs = map extend ps}
269+ other -> other
270+ where
271+ extend f = f
272+
273+ parserCommandLine :: Parser Command
274+ parserCommandLine = subparser $ mconcat
275+ [ parserOp " qq" " run a quick query" parserQuickQuery
276+ , parserOp " proc" " process a query result" (CMDProcess <$> parserProcessMode)
277+ , parserOp " testpipe" " test stdout/stdin piping a buffer" parserTestPipe
278+ , parserOp " testplot" " test plotting with dummy data" (pure CMDTestPlot )
279+ ]
280+ where
281+ parserTestPipe = CMDTestPipe <$>
282+ (flag' True (short ' r' <> help " read from stdin" ) <|> flag' False (short ' w' <> help " write to stdout" ))
283+
284+ parserProcessMode =
285+ WriteBlob <$> strOption (short ' w' <> metavar " FILE" <> help " read from stdin and also dump to file" )
286+ <|> ReadBlob <$> strOption (short ' r' <> metavar " FILE" <> help " read from file" )
287+ <|> pure PassThru
288+
289+ parserQuickQuery :: Parser Command
290+ parserQuickQuery =
291+ CMDQuery
292+ <$> pure ()
293+ <*> parseRuns
294+ <*> pure []
295+ <*> switch (short ' d' <> long " dump-only" <> help " dump result blob to stdout only; don't process" )
296+ where
297+ {-
298+ parseScriptName =
299+ strArgument (help "name of a known script" <> metavar "NAME")
300+ <|> strArgument (help "custom serialized script file" <> metavar "FILE" <> completer (bashCompleter "file"))
301+ parseParamPath =
302+ strOption $ long "param" <> metavar "JSON" <> completer (bashCompleter "file")
303+ <> help "protocol parameter file; default: data/protocol-parameters-v10.json"
304+ parseBudgetHint =
305+ option auto $ long "hint" <> metavar "BUDGET"
306+ <> help "Which budget does the script target? <Mem|Steps>"
307+ -}
308+ parseRuns =
309+ option readCommaSepList $ long " run" <> short ' r' <> metavar " run(s)"
310+ <> help " comma-separated list of run dirs or log manifest JSONs, or a single SQLite DB"
311+
312+ readCommaSepList :: Opt. ReadM [String ]
313+ readCommaSepList = Opt. maybeReader
314+ ((\ inp -> if null inp then Nothing else Just inp) . filter (not . null ) . splitOn " ," )
315+
316+ parserOp :: String -> String -> Parser a -> Mod CommandFields a
317+ parserOp c descr p = command c $ info (p <**> helper) $ progDesc descr
0 commit comments