Main.hs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. {-# LANGUAGE EmptyDataDecls #-}
  2. {-# LANGUAGE FlexibleContexts #-}
  3. {-# LANGUAGE FlexibleInstances #-}
  4. {-# LANGUAGE GADTs #-}
  5. {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  6. {-# LANGUAGE MultiParamTypeClasses #-}
  7. {-# LANGUAGE OverloadedStrings #-}
  8. {-# LANGUAGE QuasiQuotes #-}
  9. {-# LANGUAGE RankNTypes #-}
  10. {-# LANGUAGE RecordWildCards #-}
  11. {-# LANGUAGE TemplateHaskell #-}
  12. {-# LANGUAGE TypeFamilies #-}
  13. {-# LANGUAGE ViewPatterns #-}
  14. {-# LANGUAGE LambdaCase #-}
  15. {-# LANGUAGE DeriveGeneric #-}
  16. {-# OPTIONS_GHC -fno-warn-orphans #-}
  17. module Main (main, resourcesApp, Widget, WorldId) where
  18. import Control.Concurrent (runInUnboundThread)
  19. import Control.Monad (forM)
  20. import Control.Monad.Logger (runNoLoggingT)
  21. import Control.Monad.Primitive (PrimState)
  22. import Control.Monad.Trans.Resource (InternalState)
  23. import qualified Data.ByteString.Char8 as C8
  24. import Data.Pool (Pool)
  25. import Data.IORef (newIORef)
  26. import Data.Function (on)
  27. import Data.List (sortBy)
  28. import Data.Text (Text)
  29. import Database.Persist
  30. import qualified Database.Persist.Postgresql as Pg
  31. import Database.Persist.Sql
  32. import Database.Persist.TH (mkPersist, mpsGeneric,
  33. persistLowerCase, sqlSettings)
  34. import qualified Network.Wai.Handler.Warp as Warp
  35. import System.Environment (getArgs)
  36. import System.IO.Unsafe (unsafePerformIO)
  37. import qualified System.Random.MWC as R
  38. import Text.Blaze.Html
  39. import Yesod
  40. import Data.Maybe (fromJust)
  41. mkPersist sqlSettings { mpsGeneric = True } [persistLowerCase|
  42. World sql=World
  43. randomNumber Int sql=randomnumber
  44. |]
  45. mkPersist sqlSettings { mpsGeneric = True } [persistLowerCase|
  46. Fortune sql=Fortune
  47. message Text sql=message
  48. |]
  49. instance ToJSON (Entity World) where
  50. toJSON (Entity wId wRow) = object [
  51. "id" .= wId
  52. ,"randomNumber" .= (worldRandomNumber wRow)
  53. ]
  54. instance ToMarkup FortuneId where
  55. toMarkup = toMarkup . fromSqlKey
  56. data App = App
  57. { appGen :: !(R.Gen (PrimState IO))
  58. , appDbPool :: !(Pool Pg.SqlBackend)
  59. }
  60. mkYesod "App" [parseRoutes|
  61. /json JsonR GET
  62. /plaintext PlaintextR GET
  63. /db DbR GET
  64. /queries/#Int QueriesR GET
  65. !/queries/#Text DefaultQueriesR GET
  66. /fortunes FortunesR GET
  67. /updates/#Int UpdatesR GET
  68. !/updates/#Text DefaultUpdatesR GET
  69. |]
  70. fakeInternalState :: InternalState
  71. fakeInternalState = unsafePerformIO $ newIORef $ error "fakeInternalState forced"
  72. {-# NOINLINE fakeInternalState #-}
  73. instance Yesod App where
  74. makeSessionBackend _ = return Nothing
  75. {-# INLINE makeSessionBackend #-}
  76. shouldLog _ _ _ = False
  77. {-# INLINE shouldLog #-}
  78. yesodMiddleware = id
  79. {-# INLINE yesodMiddleware #-}
  80. cleanPath _ = Right
  81. {-# INLINE cleanPath #-}
  82. yesodWithInternalState _ _ = ($ fakeInternalState)
  83. {-# INLINE yesodWithInternalState #-}
  84. maximumContentLength _ _ = Nothing
  85. {-# INLINE maximumContentLength #-}
  86. getJsonR :: Handler Value
  87. getJsonR = returnJson $ object ["message" .= ("Hello, World!" :: Text)]
  88. runPg dbAction = do
  89. app <- getYesod
  90. runSqlPool dbAction (appDbPool app)
  91. getRandomRow = do
  92. app <- getYesod
  93. randomNumber <- liftIO $ ((R.uniformR (1, 10000) (appGen app)) :: IO Int)
  94. let wId = (toSqlKey $ fromIntegral randomNumber) :: WorldId
  95. get wId >>= \case
  96. Nothing -> return Nothing
  97. Just x -> return $ Just (Entity wId x)
  98. getDbR :: Handler Value
  99. getDbR = do
  100. (runPg getRandomRow) >>= \case
  101. -- TODO: Throw appropriate HTTP response
  102. Nothing -> error "This shouldn't be happening"
  103. Just worldE -> returnJson worldE
  104. getQueriesR :: Int -> Handler Value
  105. getQueriesR cnt = do
  106. result <- (runPg $ forM [1..sanitizedCnt] (\_ -> fmap fromJust getRandomRow))
  107. returnJson result
  108. where
  109. sanitizedCnt
  110. | cnt<1 = 1
  111. | cnt>500 = 500
  112. | otherwise = cnt
  113. getDefaultQueriesR :: Text -> Handler Value
  114. getDefaultQueriesR _ = getQueriesR 1
  115. getFortunesR :: Handler Html
  116. getFortunesR = do
  117. fortunesFromDb <- runPg $ selectList [] []
  118. let fortunes = sortBy (compare `on` fortuneMessage . entityVal) $ (Entity (toSqlKey 0) Fortune{fortuneMessage="Additional fortune added at request time."}):fortunesFromDb
  119. defaultLayout $ do
  120. setTitle "Fortunes"
  121. [whamlet|
  122. <table>
  123. <tr>
  124. <th>id
  125. <th>message
  126. $forall fortune <- fortunes
  127. <tr>
  128. <td>#{entityKey fortune}
  129. <td>#{fortuneMessage $ entityVal fortune}
  130. |]
  131. getUpdatesR :: Int -> Handler Value
  132. getUpdatesR cnt = do
  133. worldRows <- runPg $ forM [1..sanitizedCount] (\_ -> fmap fromJust getRandomRow)
  134. app <- getYesod
  135. updatedWorldRows <- runPg $ mapM (replaceWorldRow app) worldRows
  136. returnJson updatedWorldRows
  137. where
  138. sanitizedCount
  139. | cnt<1 = 1
  140. | cnt>500 = 500
  141. | otherwise = cnt
  142. replaceWorldRow app (Entity wId wRow) = do
  143. randomNumber <- liftIO $ ((R.uniformR (1, 10000) (appGen app)) :: IO Int)
  144. -- TODO: Should I be using replace, or update, or updateGet -- which is
  145. -- idiomatic Yesod code for this operation?
  146. let newRow = wRow{worldRandomNumber=randomNumber}
  147. replace wId newRow
  148. return (Entity wId newRow)
  149. getDefaultUpdatesR :: Text -> Handler Value
  150. getDefaultUpdatesR _ = getUpdatesR 1
  151. getPlaintextR :: Handler Text
  152. getPlaintextR = return "Hello, World!"
  153. main :: IO ()
  154. main = R.withSystemRandom $ \gen -> do
  155. [cores, host] <- getArgs
  156. let connString = ("host=" ++ host ++ " port=5432 user=benchmarkdbuser password=benchmarkdbpass dbname=hello_world")
  157. dbPool <- runNoLoggingT $ Pg.createPostgresqlPool (C8.pack connString) 256
  158. app <- toWaiAppPlain App
  159. { appGen = gen
  160. , appDbPool = dbPool
  161. }
  162. runInUnboundThread $ Warp.runSettings
  163. ( Warp.setPort 8000
  164. $ Warp.setHost "*"
  165. $ Warp.setOnException (\_ _ -> return ())
  166. Warp.defaultSettings
  167. ) app