Skip to content

Commit

Permalink
Merge #1829
Browse files Browse the repository at this point in the history
1829: Begin porting latency benchmark to shelley r=piotr-iohk a=rvl

Relates to #1825

# Overview

These refactors are the start of porting the latency bench from the byron backend to the shelley backend.

The measurement and reporting code is split into a separate module.

The bench scenario functions are reworked so that swapping backend will be easier.


Co-authored-by: Rodney Lorrimar <[email protected]>
  • Loading branch information
iohk-bors[bot] and rvl authored Jul 7, 2020
2 parents 12fafe3 + 414ba03 commit e855495
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 352 deletions.
295 changes: 103 additions & 192 deletions lib/byron/bench/Latency.hs

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions lib/byron/cardano-wallet-byron.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,6 @@ benchmark latency
, stm
, temporary
, text
, text-class
, time
build-tools:
cardano-wallet-byron
type:
Expand Down
4 changes: 4 additions & 0 deletions lib/core-integration/cardano-wallet-core-integration.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,19 @@ library
, directory
, exceptions
, extra
, fmt
, generic-lens
, hspec
, hspec-expectations-lifted
, http-api-data
, http-client
, http-types
, iohk-monitoring
, memory
, process
, retry
, scrypt
, stm
, template-haskell
, text
, text-class
Expand Down Expand Up @@ -92,3 +95,4 @@ library
Test.Integration.Scenario.CLI.Network
Test.Integration.Scenario.CLI.Port
Cardano.Wallet.TransactionSpecShared
Cardano.Wallet.LatencyBenchShared
143 changes: 143 additions & 0 deletions lib/core-integration/src/Cardano/Wallet/LatencyBenchShared.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}

module Cardano.Wallet.LatencyBenchShared
( -- * Measuring traces
withLatencyLogging
, measureApiLogs
, LogCaptureFunc

-- * Formatting results
, fmtResult
, fmtTitle
) where

import Prelude

import Cardano.BM.Backend.Switchboard
( effectuate )
import Cardano.BM.Configuration.Static
( defaultConfigStdout )
import Cardano.BM.Data.LogItem
( LOContent (..), LOMeta (..), LogObject (..) )
import Cardano.BM.Data.Severity
( Severity (..) )
import Cardano.BM.Setup
( setupTrace_, shutdown )
import Control.Concurrent.STM.TVar
( TVar, newTVarIO, readTVarIO, writeTVar )
import Control.Exception
( bracket, onException )
import Control.Monad
( mapM_, replicateM_ )
import Control.Monad.STM
( atomically )
import Data.Maybe
( mapMaybe )
import Data.Time
( NominalDiffTime )
import Data.Time.Clock
( diffUTCTime )
import Fmt
( Builder, build, fixedF, fmt, fmtLn, indentF, padLeftF, (+|), (|+) )
import Network.Wai.Middleware.Logging
( ApiLog (..), HandlerLog (..) )

import qualified Cardano.BM.Configuration.Model as CM

meanAvg :: [NominalDiffTime] -> Double
meanAvg ts = sum (map realToFrac ts) * 1000 / fromIntegral (length ts)

buildResult :: [NominalDiffTime] -> Builder
buildResult [] = "ERR"
buildResult ts = build $ fixedF 1 $ meanAvg ts

fmtTitle :: Builder -> IO ()
fmtTitle title = fmt (indentF 4 title)

fmtResult :: String -> [NominalDiffTime] -> IO ()
fmtResult title ts =
let titleExt = title|+" - " :: String
titleF = padLeftF 30 ' ' titleExt
in fmtLn (titleF+|buildResult ts|+" ms")

isLogRequestStart :: ApiLog -> Bool
isLogRequestStart = \case
ApiLog _ LogRequestStart -> True
_ -> False

isLogRequestFinish :: ApiLog -> Bool
isLogRequestFinish = \case
ApiLog _ LogRequestFinish -> True
_ -> False

measureApiLogs :: LogCaptureFunc ApiLog () -> IO a -> IO [NominalDiffTime]
measureApiLogs = measureLatency isLogRequestStart isLogRequestFinish

-- | Run tests for at least this long to get accurate timings.
sampleNTimes :: Int
sampleNTimes = 10

-- | Measure how long an action takes based on trace points and taking an
-- average of results over a short time period.
measureLatency
:: (msg -> Bool) -- ^ Predicate for start message
-> (msg -> Bool) -- ^ Predicate for end message
-> LogCaptureFunc msg () -- ^ Log capture function.
-> IO a -- ^ Action to run
-> IO [NominalDiffTime]
measureLatency start finish capture action = do
(logs, ()) <- capture $ replicateM_ sampleNTimes action
pure $ extractTimings start finish logs

-- | Scan through iohk-monitoring logs and extract time differences between
-- start and end messages.
extractTimings
:: (a -> Bool) -- ^ Predicate for start message
-> (a -> Bool) -- ^ Predicate for end message
-> [LogObject a] -- ^ Log messages
-> [NominalDiffTime]
extractTimings isStart isFinish msgs = map2 mkDiff filtered
where
map2 _ [] = []
map2 f (a:b:xs) = (f a b:map2 f xs)
map2 _ _ = error "start trace without matching finish trace"

mkDiff (False, start) (True, finish) = diffUTCTime finish start
mkDiff (False, _) _ = error "missing finish trace"
mkDiff (True, _) _ = error "missing start trace"

filtered = mapMaybe filterMsg msgs
filterMsg logObj = case loContent logObj of
LogMessage msg | isStart msg -> Just (False, getTimestamp logObj)
LogMessage msg | isFinish msg -> Just (True, getTimestamp logObj)
_ -> Nothing
getTimestamp = tstamp . loMeta


type LogCaptureFunc msg b = IO b -> IO ([LogObject msg], b)

withLatencyLogging
:: (TVar [LogObject ApiLog] -> tracers)
-> (tracers -> LogCaptureFunc ApiLog b -> IO a)
-> IO a
withLatencyLogging setupTracers action = do
tvar <- newTVarIO []
cfg <- defaultConfigStdout
CM.setMinSeverity cfg Debug
bracket (setupTrace_ cfg "bench-latency") (shutdown . snd) $ \(_, sb) -> do
action (setupTracers tvar) (logCaptureFunc tvar) `onException` do
fmtLn "Action failed. Here are the captured logs:"
readTVarIO tvar >>= mapM_ (effectuate sb) . reverse

logCaptureFunc :: TVar [LogObject ApiLog] -> LogCaptureFunc ApiLog b
logCaptureFunc tvar action = do
atomically $ writeTVar tvar []
res <- action
logs <- readTVarIO tvar
pure (reverse logs, res)
17 changes: 14 additions & 3 deletions lib/core/src/Network/Wai/Middleware/Logging.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
Expand Down Expand Up @@ -30,6 +31,8 @@ import Cardano.BM.Data.Severity
( Severity (..) )
import Cardano.BM.Data.Tracer
( HasPrivacyAnnotation (..), HasSeverityAnnotation (..) )
import Cardano.BM.Tracing
( ToObject )
import Control.Applicative
( (<|>) )
import Control.Arrow
Expand All @@ -39,7 +42,7 @@ import Control.Concurrent.MVar
import Control.Tracer
( Tracer, contramap, traceWith )
import Data.Aeson
( Value (..) )
( FromJSON (..), ToJSON (..), Value (..) )
import Data.ByteString
( ByteString )
import Data.ByteString.Builder
Expand Down Expand Up @@ -117,7 +120,7 @@ data ApiLoggerSettings = ApiLoggerSettings

-- | Just a wrapper for readability
newtype RequestId = RequestId Integer
deriving (Generic, Show, Eq)
deriving (Generic, Show, Eq, ToJSON)

-- | Create a new opaque 'ApiLoggerSettings'
newApiLoggerSettings :: IO ApiLoggerSettings
Expand Down Expand Up @@ -216,7 +219,7 @@ data ApiLog = ApiLog
-- ^ Unique integer associated with the request, for the purpose of tracing.
, logMsg :: HandlerLog
-- ^ Event trace for the handler.
} deriving (Generic, Show)
} deriving (Generic, Show, ToJSON)

instance HasPrivacyAnnotation ApiLog where
getPrivacyAnnotation (ApiLog _ msg) = getPrivacyAnnotation msg
Expand All @@ -229,6 +232,11 @@ instance ToText ApiLog where
"[" <> T.pack (show rid) <> "] "
<> toText msg

-- These instance are required by iohk-monitoring
instance ToObject ApiLog
instance FromJSON ApiLog where
parseJSON _ = fail "FromJSON ApiLog stub"

-- | Tracer events related to the handling of a single request.
data HandlerLog
= LogRequestStart
Expand Down Expand Up @@ -257,6 +265,9 @@ instance ToText HandlerLog where
LogResponseBody body -> T.decodeUtf8 body
LogRequestFinish -> "Completed response to API request"

instance ToJSON HandlerLog where
toJSON = String . toText

-- | Removes sensitive details from valid request payloads and completely
-- obfuscate invalid payloads.
sanitize :: [Text] -> ByteString -> Text
Expand Down
2 changes: 0 additions & 2 deletions lib/jormungandr/cardano-wallet-jormungandr.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,6 @@ benchmark latency
, stm
, temporary
, text
, text-class
, time
build-tools:
cardano-wallet-jormungandr
type:
Expand Down
Loading

0 comments on commit e855495

Please sign in to comment.