{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE ScopedTypeVariables #-} module Main where import Squeal.PostgreSQL hiding ( defaultMain ) import Gauge.Main import Gauge.Main.Options ( defaultConfig , Config(..) , Verbosity(..) , DisplayMode(..) , Mode(..) ) import GHC.Generics import qualified Generics.SOP as SOP import Test.QuickCheck -- For CI import Main.Utf8 ( withUtf8 ) -- For keeping a track of which question ID to query import Data.Int ( Int64 ) import Data.IORef -- Project imports import Gauge.Schema import Gauge.Queries import Gauge.DBSetup ( teardownDB ) import Gauge.DBHelpers ( initDBWithPool , getRandomUser , runDbWithPool , SquealPool ) main :: IO () main = do -- A mutable hack here to keep track of -- pulling a new user by ID from the db instead of the same id currentId <- newIORef (1 :: UserId) -- Define benchmarks let queryRenderGroup :: Benchmark queryRenderGroup = bgroup "Render Queries" [ bench "createUser: weak head normal form" $ whnf renderSQL createUser , bench "createUser: normal form" $ nf renderSQL createUser , bench "userDetails: weak head normal form" $ whnf renderSQL userDetails , bench "userDetails: normal form" $ nf renderSQL userDetails , bench "insertDeviceDetails: weak head normal form" $ whnf renderSQL insertDeviceDetails , bench "insertDeviceDetails: normal form" $ nf renderSQL insertDeviceDetails ] -- Queries against an actual DB -- 1. Initialize Schema to DB -- 2. Make connection pool and pass it to tests -- 3. Generate users on the fly and add them to DB -- 4. Tear the Schema down from the DB dbInsertsGroup :: Benchmark dbInsertsGroup = envWithCleanup initDBWithPool (const teardownDB) $ \pool -> bgroup "Run individual INSERTs against DB using a connection pool" [ bgroup "INSERT: add users to the table users" [ bench "Run individual INSERT statement" $ makeRunOnce $ perRunEnv getRandomUser -- The actual action to benchmark (\(user :: InsertUser) -> runDbWithPool pool $ createUserSession user ) ] ] dbSelectsGroup :: Benchmark dbSelectsGroup = envWithCleanup initDBWithPool (const teardownDB) $ \pool -> bgroup "Run individual SELECTs against DB using a connection pool" [ bgroup "SELECT: fetch users from the table users individually" [ bench "Fetch a single user" $ makeRunOnce $ perRunEnv (insertAndIncrement pool currentId) (\(id_ :: UserId) -> runDbWithPool pool $ userDetailsSession id_ ) ] ] withUtf8 $ defaultMain [queryRenderGroup, dbInsertsGroup, dbSelectsGroup] -- | Configure the benchmark to run only once (per IO action) makeRunOnce :: Benchmarkable -> Benchmarkable makeRunOnce current = current { perRun = True } getAndIncrementId :: (IORef UserId) -> IO UserId getAndIncrementId currentId = do current <- readIORef currentId writeIORef currentId (current + 1) return current -- | This INSERTs a row in the db so that there's always a row to query. -- Otherwise 'getRow 0' throws an exception. -- NOTE: will make benchmark time slower but does not affect results. insertAndIncrement :: SquealPool -> (IORef UserId) -> IO UserId insertAndIncrement pool currentId = do user <- getRandomUser _ <- runDbWithPool pool $ createUserSession user getAndIncrementId currentId