Skip to content

Commit 5ab000b

Browse files
committed
locli-quick: queries on WB_REMOTE (WIP)
1 parent 986d049 commit 5ab000b

File tree

10 files changed

+313
-51
lines changed

10 files changed

+313
-51
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ show-profile: ## NAME=profile-name
7171
ps: ## Plain-text list of profiles
7272
@nix build .#workbench.profile-names-json --json | jq '.[0].outputs.out' -r | xargs jq '.[]' --raw-output
7373

74+
locli-qq:
75+
@test -n "${LOCLI_QQ}" || { echo 'locli-qq: please specify query to run as LOCLI_QQ="...query..."' && exit 1; }
76+
@nix run .#locli-quick -- testpipe ${LOCLI_QQ}
77+
7478
##
7579
## Profile-based cluster shells (autogenerated targets)
7680
##

bench/locli/app/locli-quick.hs

Lines changed: 169 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
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

78
import Cardano.Api (ExceptT, SlotNo (..), runExceptT)
89

@@ -11,34 +12,44 @@ import Cardano.Analysis.Reducer
1112
import Cardano.Analysis.Reducer.Util
1213
import Cardano.Unlog.BackendDB
1314
import Cardano.Unlog.BackendFile (readRunLogsBare)
14-
import Cardano.Unlog.LogObject (LogObject (..), RunLogs (..), rlLogs)
15+
import Cardano.Unlog.LogObject (LogObject (..), RunLogs (..), hlLogs, rlLogs)
1516
import Cardano.Unlog.LogObjectDB
1617
import Cardano.Util hiding (toDouble)
1718

1819
import Prelude hiding (log, seq)
1920

20-
import Data.Bifunctor (first)
21+
import Data.Bifunctor
22+
import qualified Data.ByteString as B
2123
import Data.Either
2224
import Data.Function (on)
2325
import 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
2529
import Data.Reducer
2630
import qualified Data.Set as Set (fromList, null)
2731
import Data.Word
28-
import System.Environment (getArgs)
32+
import Options.Applicative as Opt
33+
import System.FilePath (splitDirectories, takeExtension)
2934

3035
import Database.Sqlite.Easy hiding (Text)
3136
import Graphics.EasyPlot
3237

3338

3439
main :: IO ()
3540
main = 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
4455
testPlot :: 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

90135
runOnRun :: forall l. LoadFromDB l => l -> FilePath -> [String] -> IO (RunLogs [LoadResult l])
91136
runOnRun 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

151196
data LoadHeapData = LoadHeapData
152197

198+
199+
-- TODO: use timestamp to infer slot numbers during startup
153200
instance 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

bench/locli/locli.cabal

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ executable locli-quick
176176

177177
other-modules: Cardano.Analysis.Reducer
178178
Cardano.Analysis.Reducer.Util
179+
Cardano.Analysis.Quick.Types
179180
Data.Reducer
180181
Graphics.EasyPlot
181182

@@ -191,7 +192,10 @@ executable locli-quick
191192
, containers
192193
, directory
193194
, extra
195+
, filepath
194196
, process
197+
, optparse-applicative-fork
198+
, serialise
195199
, split
196200
, text
197201
, text-short
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{-# LANGUAGE DeriveAnyClass #-}
2+
{-# LANGUAGE DeriveGeneric #-}
3+
{-# LANGUAGE DerivingVia #-}
4+
{-# LANGUAGE FlexibleInstances #-}
5+
{-# LANGUAGE StandaloneDeriving #-}
6+
7+
{-# OPTIONS_GHC -Wno-orphans -Wno-partial-fields #-}
8+
9+
module Cardano.Analysis.Quick.Types
10+
(module Cardano.Analysis.Quick.Types)
11+
where
12+
13+
import Cardano.Analysis.API.Ground (Host (..), JsonLogfile (..), LogObjectSource (..))
14+
import Cardano.Unlog.BackendDB (LoadRawResult (..))
15+
import Cardano.Unlog.LogObject (HostLogs, RunLogs)
16+
import Cardano.Unlog.LogObjectDB (SummaryDB (..))
17+
import Cardano.Util (I)
18+
19+
import Codec.Serialise (Serialise (..))
20+
import Data.Profile (ProfileEntry)
21+
import Data.Text.Short (ShortText, fromShortByteString, toShortByteString)
22+
import GHC.Generics (Generic)
23+
24+
import Database.Sqlite.Easy (SQLData)
25+
26+
27+
instance Serialise ShortText where
28+
encode = encode . toShortByteString
29+
decode = decode >>= maybe (fail "fromShortByteString: not a ShortText") pure . fromShortByteString
30+
31+
deriving via ShortText instance Serialise Host
32+
deriving via FilePath instance Serialise JsonLogfile
33+
deriving instance Serialise LogObjectSource
34+
35+
deriving instance Generic SummaryDB
36+
deriving instance Serialise SummaryDB
37+
38+
deriving instance Serialise SQLData
39+
40+
deriving instance Generic LoadRawResult
41+
deriving instance Serialise LoadRawResult
42+
43+
-- quick queries (and their result type) do not support encoding GHC profiling data
44+
instance {-# OVERLAPS #-} Serialise [ProfileEntry I] where
45+
encode = mempty
46+
decode = pure []
47+
48+
deriving instance Serialise a => Serialise (HostLogs a)
49+
deriving instance Serialise a => Serialise (RunLogs a)
50+
51+
{-
52+
data RemoteQueryMeta = QueryRemote
53+
{ qrmRunIds :: [String]
54+
, qrmNodes :: [Host]
55+
, qrmQuery :: ()
56+
}
57+
-}
58+
59+
data RemoteQueryResult
60+
= RemoteQueryResult
61+
{ rqrQuery :: ()
62+
, rqrResult :: RunLogs LoadRawResult
63+
}
64+
| RemoteQueryError
65+
{ rqrError :: String
66+
}
67+
deriving (Generic, Serialise)

0 commit comments

Comments
 (0)