Skip to content

Commit f3e394a

Browse files
committed
Display memory stats in bench-e2e benchmark
Signed-off-by: Sasha Bogicevic <sasha.bogicevic@iohk.io>
1 parent a22b272 commit f3e394a

File tree

6 files changed

+90
-45
lines changed

6 files changed

+90
-45
lines changed

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@
160160
pkgs.fourmolu
161161
];
162162
treefmt = pkgs.treefmt;
163+
# dstat = pkgs.dstat;
163164
};
164165
} // lib.attrsets.mergeAttrsList (map (x: componentsToWerrors x hsPkgs.${x}) [
165166
"hydra-cardano-api"

hydra-cluster/bench/Bench/EndToEnd.hs

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Bench.EndToEnd where
55
import Hydra.Prelude
66
import Test.Hydra.Prelude
77

8-
import Bench.Summary (Summary (..), makeQuantiles)
8+
import Bench.Summary (Summary (..), SystemStats, makeQuantiles)
99
import CardanoClient (RunningNode (..), awaitTransaction, submitTransaction, submitTx)
1010
import CardanoNode (findRunningCardanoNode', withCardanoNodeDevnet)
1111
import Control.Concurrent.Class.MonadSTM (
@@ -17,6 +17,7 @@ import Control.Concurrent.Class.MonadSTM (
1717
newTVarIO,
1818
tryReadTBQueue,
1919
writeTBQueue,
20+
writeTVar,
2021
)
2122
import Control.Lens (to, (^..), (^?))
2223
import Control.Monad.Class.MonadAsync (mapConcurrently)
@@ -27,6 +28,7 @@ import Data.Map qualified as Map
2728
import Data.Scientific (Scientific)
2829
import Data.Set ((\\))
2930
import Data.Set qualified as Set
31+
import Data.Text (pack)
3032
import Data.Time (UTCTime (UTCTime), utctDayTime)
3133
import Hydra.Cardano.Api (NetworkId, SocketPath, Tx, TxId, UTxO, getVerificationKey, signTx)
3234
import Hydra.Cluster.Faucet (FaucetLog (..), publishHydraScriptsAs, returnFundsToFaucet', seedFromFaucet)
@@ -60,7 +62,7 @@ import HydraNode (
6062
)
6163
import System.Directory (findExecutable)
6264
import System.FilePath ((</>))
63-
import System.IO (hGetLine, hPutStrLn)
65+
import System.IO (hGetLine)
6466
import System.Process (
6567
CreateProcess (..),
6668
StdStream (CreatePipe),
@@ -70,9 +72,8 @@ import System.Process (
7072
import Test.HUnit.Lang (formatFailureReason)
7173
import Text.Printf (printf)
7274
import Text.Regex.TDFA (getAllTextMatches, (=~))
73-
import Prelude (read)
7475

75-
bench :: Int -> NominalDiffTime -> FilePath -> Dataset -> IO Summary
76+
bench :: Int -> NominalDiffTime -> FilePath -> Dataset -> IO (Summary, SystemStats)
7677
bench startingNodeId timeoutSeconds workDir dataset = do
7778
putStrLn $ "Test logs available in: " <> (workDir </> "test.log")
7879
withFile (workDir </> "test.log") ReadWriteMode $ \hdl ->
@@ -81,7 +82,8 @@ bench startingNodeId timeoutSeconds workDir dataset = do
8182
putTextLn "Starting benchmark"
8283
let cardanoKeys = hydraNodeKeys dataset <&> \sk -> (getVerificationKey sk, sk)
8384
let hydraKeys = generateSigningKey . show <$> [1 .. toInteger (length cardanoKeys)]
84-
withOSStats workDir $
85+
statsTvar <- newTVarIO mempty
86+
scenarioData <- withOSStats workDir statsTvar $
8587
withCardanoNodeDevnet (contramap FromCardanoNode tracer) workDir $ \node@RunningNode{nodeSocket} -> do
8688
putTextLn "Seeding network"
8789
seedNetwork node dataset (contramap FromFaucet tracer)
@@ -94,6 +96,8 @@ bench startingNodeId timeoutSeconds workDir dataset = do
9496
withHydraCluster hydraTracer workDir nodeSocket startingNodeId cardanoKeys hydraKeys hydraScriptsTxId contestationPeriod depositDeadline $ \clients -> do
9597
waitForNodesConnected hydraTracer 20 clients
9698
scenario hydraTracer node workDir dataset clients
99+
systemStats <- readTVarIO statsTvar
100+
pure (scenarioData, systemStats)
97101

98102
benchDemo ::
99103
NetworkId ->
@@ -102,13 +106,13 @@ benchDemo ::
102106
[Host] ->
103107
FilePath ->
104108
Dataset ->
105-
IO Summary
109+
IO (Summary, SystemStats)
106110
benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Dataset{clientDatasets} = do
107111
putStrLn $ "Test logs available in: " <> (workDir </> "test.log")
108112
withFile (workDir </> "test.log") ReadWriteMode $ \hdl ->
109113
withTracerOutputTo hdl "Test" $ \tracer ->
110114
failAfter timeoutSeconds $ do
111-
putTextLn "Starting benchmark"
115+
putTextLn "Starting benchmark demo"
112116
let cardanoTracer = contramap FromCardanoNode tracer
113117
findRunningCardanoNode' cardanoTracer networkId nodeSocket >>= \case
114118
Nothing ->
@@ -122,7 +126,7 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas
122126
withHydraClientConnections hydraTracer (hydraClients `zip` [1 ..]) [] $ \case
123127
[] -> error "no hydra clients provided"
124128
(leader : followers) ->
125-
scenario hydraTracer node workDir dataset (leader :| followers)
129+
(,[]) <$> scenario hydraTracer node workDir dataset (leader :| followers)
126130
where
127131
withHydraClientConnections tracer apiHosts connections action = do
128132
case apiHosts of
@@ -221,47 +225,67 @@ defaultDescription = ""
221225
-- __NOTE__: This function relies on [dstat](https://linux.die.net/man/1/dstat). If the executable is not in the @PATH@
222226
-- it's basically a no-op.
223227
--
224-
-- Writes a @system.csv@ file into given `workDir` containing one line every 5 second with share of user CPU load.
228+
-- Writes into given `TVar` containing one line every 5 second with share of user/free memory load.
225229
-- Here is a sample content:
226230
--
227231
-- @@
228-
-- 2022-02-16 14:25:43.67203351 UTC,11
229-
-- 2022-02-16 14:25:48.669817664 UTC,10
230-
-- 2022-02-16 14:25:53.672050421 UTC,14
231-
-- 2022-02-16 14:25:58.670460796 UTC,12
232-
-- 2022-02-16 14:26:03.669831775 UTC,11
233-
-- 2022-02-16 14:26:08.67203726 UTC,10
232+
-- 2025-02-04 17:22:50.30543862 UTC
233+
-- Used: 7648M, Free: 46.9G
234+
--2025-02-04 17:22:55.305513945 UTC
235+
-- Used: 7713M, Free: 46.9G
236+
--2025-02-04 17:23:00.30550915 UTC
237+
-- Used: 7715M, Free: 46.9G
238+
--2025-02-04 17:23:05.305513574 UTC
239+
-- Used: 7717M, Free: 46.8G
240+
--2025-02-04 17:23:10.305538265 UTC
241+
-- Used: 7718M, Free: 46.8G
242+
--2025-02-04 17:23:15.305519942 UTC
243+
-- Used: 7719M, Free: 46.8G
244+
--2025-02-04 17:23:20.30550604 UTC
245+
-- Used: 7722M, Free: 46.8G
246+
--2025-02-04 17:23:25.305413146 UTC
247+
-- Used: 7723M, Free: 46.8G
234248
-- ...
235249
-- @@
236250
--
237251
-- TODO: add more data points for memory and network consumption
238-
withOSStats :: FilePath -> IO a -> IO a
239-
withOSStats workDir action =
252+
withOSStats :: FilePath -> TVar IO SystemStats -> IO a -> IO a
253+
withOSStats workDir tvar action =
240254
findExecutable "dstat" >>= \case
241255
Nothing -> action
242-
Just exePath ->
243-
withCreateProcess (process exePath){std_out = CreatePipe} $ \_stdin out _stderr _processHandle ->
256+
Just _ ->
257+
withCreateProcess process{std_out = CreatePipe} $ \_stdin out _stderr _processHandle ->
244258
race
245-
(collectStats out $ workDir </> "system.csv")
259+
(collectStats tvar out)
246260
action
247261
>>= \case
248-
Left () -> failure "dstat process failed unexpectedly"
262+
Left _ -> failure "dstat process failed unexpectedly"
249263
Right a -> pure a
250264
where
251-
process exePath = (proc exePath ["-cm", "-n", "-N", "lo", "--integer", "--noheaders", "--noupdate", "5"]){cwd = Just workDir}
252-
253-
collectStats Nothing _ = pure ()
254-
collectStats (Just hdl) filepath =
255-
withFile filepath WriteMode $ \file ->
256-
forever $
257-
hGetLine hdl >>= processStat file
258-
259-
processStat :: Handle -> String -> IO ()
260-
processStat file stat =
261-
case getAllTextMatches (stat =~ ("[0-9]+" :: String)) :: [String] of
262-
(cpu : _) -> do
265+
process = (proc "dstat" ["-cm", "-n", "-N", "lo", "--noheaders", "--noupdate", "5"]){cwd = Just workDir}
266+
267+
collectStats _ Nothing = pure ()
268+
collectStats tvar' (Just hdl) =
269+
forever $
270+
hGetLine hdl >>= processStat tvar'
271+
272+
processStat :: TVar IO [Text] -> String -> IO ()
273+
processStat tvar' stat = do
274+
let matches = getAllTextMatches (stat =~ ("[0-9.]+.|([A-Z])" :: String)) :: [String]
275+
case matches of
276+
(_ : _ : _ : _ : _ : memUsed : memFree : _) -> do
263277
now <- getCurrentTime
264-
hPutStrLn file $ show now <> "," <> show ((read cpu :: Double) / 100)
278+
let str =
279+
pack $
280+
show now
281+
<> "\n\t"
282+
<> "Used: "
283+
<> memUsed
284+
<> ", "
285+
<> "Free: "
286+
<> memFree
287+
stats <- readTVarIO tvar'
288+
atomically $ writeTVar tvar' $ stats <> [str]
265289
_ -> pure ()
266290

267291
-- | Compute average confirmation/validation time over intervals of 5 seconds.

hydra-cluster/bench/Bench/Summary.hs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import Text.Printf (printf)
1717

1818
type Percent = Double
1919

20+
type SystemStats = [Text]
21+
2022
data Summary = Summary
2123
{ clusterSize :: Word64
2224
, totalTxs :: Int
@@ -50,8 +52,8 @@ makeQuantiles :: [NominalDiffTime] -> Vector Double
5052
makeQuantiles times =
5153
Statistics.quantilesVec def (fromList [0 .. 99]) 100 (fromList $ map (fromRational . (* 1000) . toRational . nominalDiffTimeToSeconds) times)
5254

53-
textReport :: Summary -> [Text]
54-
textReport Summary{totalTxs, numberOfTxs, averageConfirmationTime, quantiles, numberOfInvalidTxs} =
55+
textReport :: (Summary, [Text]) -> [Text]
56+
textReport (Summary{totalTxs, numberOfTxs, averageConfirmationTime, quantiles, numberOfInvalidTxs}, systemStats) =
5557
let frac :: Double
5658
frac = 100 * fromIntegral numberOfTxs / fromIntegral totalTxs
5759
in [ pack $ printf "Confirmed txs/Total expected txs: %d/%d (%.2f %%)" numberOfTxs totalTxs frac
@@ -66,8 +68,11 @@ textReport Summary{totalTxs, numberOfTxs, averageConfirmationTime, quantiles, nu
6668
else []
6769
)
6870
++ ["Invalid txs: " <> show numberOfInvalidTxs]
71+
++ ["| Memory data | "]
72+
++ [" "]
73+
++ [unlines systemStats]
6974

70-
markdownReport :: UTCTime -> [Summary] -> [Text]
75+
markdownReport :: UTCTime -> [(Summary, [Text])] -> [Text]
7176
markdownReport now summaries =
7277
pageHeader <> concatMap formattedSummary summaries
7378
where
@@ -98,8 +103,8 @@ markdownReport now summaries =
98103
, ""
99104
]
100105

101-
formattedSummary :: Summary -> [Text]
102-
formattedSummary Summary{clusterSize, numberOfTxs, averageConfirmationTime, quantiles, summaryTitle, summaryDescription, numberOfInvalidTxs} =
106+
formattedSummary :: (Summary, [Text]) -> [Text]
107+
formattedSummary (Summary{clusterSize, numberOfTxs, averageConfirmationTime, quantiles, summaryTitle, summaryDescription, numberOfInvalidTxs}, systemStats) =
103108
[ ""
104109
, "## " <> summaryTitle
105110
, ""
@@ -120,6 +125,9 @@ markdownReport now summaries =
120125
)
121126
++ [ "| _Number of Invalid txs_ | " <> show numberOfInvalidTxs <> " |"
122127
]
128+
++ [" "]
129+
++ ["| Memory data | "]
130+
++ [unlines systemStats]
123131

124132
nominalDiffTimeToMilliseconds :: NominalDiffTime -> Nano
125133
nominalDiffTimeToMilliseconds = fromRational . (* 1000) . toRational . nominalDiffTimeToSeconds

hydra-cluster/bench/Main.hs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Test.Hydra.Prelude
77

88
import Bench.EndToEnd (bench, benchDemo)
99
import Bench.Options (Options (..), benchOptionsParser)
10-
import Bench.Summary (Summary (..), errorSummary, markdownReport, textReport)
10+
import Bench.Summary (SystemStats, Summary (..), errorSummary, markdownReport, textReport)
1111
import Data.Aeson (eitherDecodeFileStrict', encodeFile)
1212
import Hydra.Cluster.Fixture (Actor (..))
1313
import Hydra.Cluster.Util (keysFor)
@@ -71,19 +71,19 @@ main = do
7171
withArgs [] $ do
7272
try @_ @HUnitFailure (action dir dataset) >>= \case
7373
Left exc -> pure $ Left (dataset, dir, errorSummary dataset exc, TestFailed exc)
74-
Right summary@Summary{totalTxs, numberOfTxs, numberOfInvalidTxs}
74+
Right (summary@Summary{totalTxs, numberOfTxs, numberOfInvalidTxs}, systemStats)
7575
| numberOfTxs /= totalTxs -> pure $ Left (dataset, dir, summary, NotEnoughTransactions numberOfTxs totalTxs)
76-
| numberOfInvalidTxs == 0 -> pure $ Right summary
76+
| numberOfInvalidTxs == 0 -> pure $ Right (summary, systemStats)
7777
| otherwise -> pure $ Left (dataset, dir, summary, InvalidTransactions numberOfInvalidTxs)
7878

79-
summarizeResults :: Maybe FilePath -> [Either (Dataset, FilePath, Summary, BenchmarkFailed) Summary] -> IO ()
79+
summarizeResults :: Maybe FilePath -> [Either (Dataset, FilePath, Summary, BenchmarkFailed) (Summary, SystemStats)] -> IO ()
8080
summarizeResults outputDirectory results = do
8181
let (failures, summaries) = partitionEithers results
8282
case failures of
8383
[] -> writeBenchmarkReport outputDirectory summaries
8484
errs -> do
8585
forM_ errs $ \(_, dir, summary, exc) -> do
86-
writeBenchmarkReport outputDirectory [summary]
86+
writeBenchmarkReport outputDirectory [(summary, [])]
8787
benchmarkFailedWith dir exc
8888
exitFailure
8989

@@ -121,7 +121,7 @@ benchmarkFailedWith benchDir = \case
121121
where
122122
formatLocation = maybe "" (\loc -> "at " <> prettySrcLoc loc)
123123

124-
writeBenchmarkReport :: Maybe FilePath -> [Summary] -> IO ()
124+
writeBenchmarkReport :: Maybe FilePath -> [(Summary, SystemStats)] -> IO ()
125125
writeBenchmarkReport outputDirectory summaries = do
126126
dumpToStdout
127127
whenJust outputDirectory writeReport

hydra-prelude/src/Hydra/Prelude.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ module Hydra.Prelude (
3737
decodeBase16,
3838
(?>),
3939
withFile,
40+
withFileRetry,
4041
spy,
4142
spy',
4243
) where
@@ -150,6 +151,7 @@ import Relude.Extra.Map (
150151
keys,
151152
)
152153
import System.IO qualified
154+
import System.IO.Error (userError)
153155
import Test.QuickCheck (
154156
Arbitrary (..),
155157
Gen,
@@ -248,6 +250,15 @@ withFile fp mode action =
248250
Left (e :: IOException) -> throwIO e
249251
Right x -> pure x
250252

253+
withFileRetry :: Int -> FilePath -> IOMode -> (Handle -> IO a) -> IO a
254+
withFileRetry n fp mode action =
255+
if n == 0
256+
then throwIO $ userError "withFileRetry: too many retries"
257+
else
258+
System.IO.withFile fp mode (try . action) >>= \case
259+
Left (_ :: IOException) -> withFileRetry (n - 1) fp mode action
260+
Right x -> pure x
261+
251262
-- | Like 'traceShow', but with pretty printing of the value.
252263
{-# WARNING spy "Use for debugging purposes only" #-}
253264
spy :: Show a => a -> a

nix/hydra/shell.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ let
5353
pkgs.weeder
5454
pkgs.yarn
5555
pkgs.yq
56+
pkgs.dstat
5657
];
5758

5859
libs = [

0 commit comments

Comments
 (0)