mariadb-haskell-0.1.0.0: validation/hs/Validation/Stage5Bench.hs
{-# LANGUAGE BangPatterns #-}
-- | Streaming CDC workloads. Fixture encoding is outside decoder timing; the
-- framing/CRC profile deliberately includes encoding fresh, varying messages.
-- Live profiles include connect/setup, decoding and checkpoint observation, with
-- source log creation outside timing. All row values contribute to a checksum.
module Validation.Stage5Bench (run) where
import Control.Exception (evaluate)
import Control.Monad
import qualified Codec.Compression.Zlib as Z
import Data.Binary.Put
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import Data.IORef
import Data.List (intercalate)
import qualified Data.Text.Encoding as TE
import Data.Word
import Database.MySQL.Base
import Database.MySQL.BinLog
import GHC.Clock
import GHC.Stats
import System.Mem
import qualified System.IO.Streams as S
import Validation.Common
run :: String -> Int -> IO ()
run ident count
| count <= 0 = fail "positive CDC benchmark count required"
| ident == "cdc.decode.single-table" = measure ident count $ decodeRows
(mapping 1 : [row 1 n | n <- [1..count]])
| ident == "cdc.decode.interleaved" = measure ident count $ decodeRows
([mapping tid | tid <- [1..64]] ++ [row (fromIntegral (n `mod` 64 + 1)) n | n <- [1..count]])
| ident == "cdc.decode.sparse-update" = measure ident count $ do
let metadata=make BINLOG_TABLE_MAP_EVENT $ do
putWord48le 1 >> putWord16le 0
putWord8 1 >> putByteString "d" >> putWord8 0
putWord8 1 >> putByteString "t" >> putWord8 0
putLenEncInt 64 >> putByteString (B.replicate 64 3) >> putLenEncInt 0 >> putWord64le 0
update n = make BINLOG_UPDATE_ROWS_EVENTv1 $ do
putWord48le 1 >> putWord16le 0 >> putLenEncInt 64
putWord64le 1 >> putWord64le 0x8000000000000000
putWord8 0 >> putWord32le (fromIntegral (negate n))
putWord8 0 >> putWord32le (fromIntegral n)
decodeRows (metadata : map update [1..count])
| ident == "cdc.decode.compressed" = measure ident count $ decodeRows
(mapping 1 : [compressedRow n | n <- [1..count]])
| ident == "cdc.framing.crc32" = measure ident count $ do
total <- foldM (\ !acc n -> do
let event=row 1 n
raw=runPut $ do
putWord32le 0 >> putWord8 23 >> putWord32le 1
putWord32le (fromIntegral (23 + L.length (blBody event))) >> putWord32le 100 >> putWord16le 0
putLazyByteString (blBody event)
framed=putToPacket 1 (putWord8 0 >> putLazyByteString raw >> putWord32le (binLogCRC32 raw))
checked <- getFromPacket (getBinLogPacket True False) framed
unless (blBody checked == blBody event) (fail "CRC fixture roundtrip")
pure (acc + 1)) (0 :: Integer) [1..count]
pure total
| ident == "cdc.retention" = measure ident count $ do
cursor <- newIORef (0 :: Int)
input <- S.makeInputStream $ do
n <- atomicModifyIORef' cursor (\value -> (value+1,value))
pure (if n==0 then Just (mapping 1) else if n<=count then Just (row 1 n) else Nothing)
gtids <- either fail pure (gtidSet [])
(events,_) <- newCDCDecoder defaultDecodeLimits (Just gtids) fixtureFormat "bin.1" input
let loop :: Int -> Integer -> IO Integer
loop !n !total = do
event <- S.read events
case event of
Nothing -> pure total
Just value -> do
addition <- case cdcEventPayload value of
CDCWrite _ image -> case writeRowData image of
[[BinLogLong valueNumber]] -> pure (toInteger valueNumber)
_ -> fail "retention image mismatch"
_ -> fail "retention event mismatch"
let next=n+1
when (next `elem` [1000,10000,100000,1000000]) $ do
performGC
stats <- getRTSStats
putStrLn (intercalate "\t" ["STAGE5_HEAP",show next,show (gcdetails_live_bytes (gc stats))])
loop next (total+addition)
loop 0 0
| ident == "cdc.live.rows" || ident == "cdc.live.checkpoints" = withConn $ \writer -> do
exec writer "DROP TABLE IF EXISTS mb_stage5_bench"
exec writer "CREATE TABLE mb_stage5_bench(id INT PRIMARY KEY,n INT) ENGINE=InnoDB"
(_,stateStream) <- query_ writer "SELECT @@global.gtid_binlog_pos"
state <- S.toList stateStream
start <- case state of
[[MySQLText value]] -> either fail pure (parseGtidSet (TE.encodeUtf8 value))
_ -> fail "CDC benchmark GTID state"
if ident == "cdc.live.rows" then withTransaction writer $ insertRows writer count
else forM_ [1..count] $ \n -> withTransaction writer $ void $ execute writer
"INSERT INTO mb_stage5_bench VALUES(?,1)" [MySQLInt32 (fromIntegral n)]
measure ident count $ withConn $ \source -> do
stream <- openCDC source 610130 (defaultCDCOptions (FromGtid start)) {cdcNonBlocking=True}
(checksum,checkpoints) <- consume (cdcEvents stream)
equal "live checkpoint count" (if ident=="cdc.live.rows" then 1 else count) checkpoints
pure checksum
| otherwise = fail "unknown CDC benchmark profile"
where
insertRows conn n = forM_ [1..n] $ \key -> void $ execute conn
"INSERT INTO mb_stage5_bench VALUES(?,1)" [MySQLInt32 (fromIntegral key)]
fixtureFormat :: FormatDescription
fixtureFormat = FormatDescription 4 "12.3.3-MariaDB" 0 (B.replicate 172 8)
make :: BinLogEventType -> Put -> BinLogPacket
make typ builder = BinLogPacket 0 typ 1 (fromIntegral (19+L.length body)) 100 0 body False
where body=runPut builder
mapping :: Word64 -> BinLogPacket
mapping tid = make BINLOG_TABLE_MAP_EVENT $ do
putWord48le tid >> putWord16le 0
putWord8 1 >> putByteString "d" >> putWord8 0
putWord8 1 >> putByteString "t" >> putWord8 0
putLenEncInt 1 >> putWord8 3 >> putLenEncInt 0 >> putWord8 0
row :: Word64 -> Int -> BinLogPacket
row tid n = make BINLOG_WRITE_ROWS_EVENTv1 $ do
putWord48le tid >> putWord16le 0 >> putLenEncInt 1 >> putWord8 1 >> putWord8 0
putWord32le (fromIntegral n)
compressedRow :: Int -> BinLogPacket
compressedRow n = let event=row 1 n in event {blEventType=MARIADB_WRITE_ROWS_COMPRESSED_EVENTv1,
blBody=L.take 10 (blBody event) <> L.pack [0x81,5] <> Z.compress (L.drop 10 (blBody event))}
decodeRows :: [BinLogPacket] -> IO Integer
decodeRows packets = do
input <- S.fromList packets
gtids <- either fail pure (gtidSet [])
(events,_) <- newCDCDecoder defaultDecodeLimits (Just gtids) fixtureFormat "bin.1" input
fst <$> consume events
consume :: S.InputStream CDCEvent -> IO (Integer,Int)
consume input = loop 0 0
where
loop !total !checkpoints = do
value <- S.read input
case value of
Nothing -> pure (total,checkpoints)
Just event -> do
let count=case cdcEventPayload event of
CDCWrite _ rowsEvent -> sum [toInteger n | image <- writeRowData rowsEvent,
BinLogLong n <- [last image]]
CDCUpdate _ updateEvent -> sum [toInteger after | ([BinLogLong before],[BinLogLong after]) <- updateRowData updateEvent,
before == negate after]
_ -> 0
commits=maybe 0 (const 1) (cdcEventCheckpoint event)
loop (total+count) (checkpoints+commits)
measure :: String -> Int -> IO Integer -> IO ()
measure ident units operation = do
void operation
void operation
performGC
before <- getRTSStats
start <- getMonotonicTimeNSec
checksum <- operation >>= evaluate
end <- getMonotonicTimeNSec
performGC
after <- getRTSStats
let expected | ident `elem` ["cdc.decode.single-table","cdc.decode.interleaved","cdc.decode.compressed","cdc.decode.sparse-update","cdc.retention"] = toInteger units*(toInteger units+1) `div` 2
| otherwise = toInteger units
equal "CDC benchmark checksum" expected checksum
putStrLn (intercalate "\t" ["STAGE5",ident,show units,show (end-start),
show (allocated_bytes after-allocated_bytes before),show (cpu_ns after-cpu_ns before),
show (gc_cpu_ns after-gc_cpu_ns before),show checksum])