~nmh/log-parser-18xx

2e34a0b44cad03dc3535d451a0d4aa7bf56a69f0 — nick hansen 3 years ago 738247f
add some metrics
M 18xx-log-parser.cabal => 18xx-log-parser.cabal +3 -0
@@ 46,6 46,9 @@ library
                     , shakespeare
                     , http-types
                     , warp
                     , prometheus-client >= 1.0.1
                     , wai-middleware-prometheus
                     , prometheus-metrics-ghc
  default-language:    Haskell2010
  if flag(dev)
     cpp-options: -DDEV

M src/LogParser/Cache.hs => src/LogParser/Cache.hs +64 -16
@@ 1,3 1,5 @@
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# OPTIONS_GHC -Wno-unused-top-binds #-}


@@ 21,9 23,12 @@ import Data.Function
import Data.Hashable
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)
import Data.Time.Clock
import Prometheus

type Id = Int



@@ 33,22 38,39 @@ data CacheData a = CD { dataStore :: Map Id (a,UTCTime)
                      , maxDataCount :: Int
                      }

newtype Cache a = Cache { unwrapCache :: TVar (CacheData a) }


emptyCache :: NominalDiffTime -> Int -> IO (Cache a)
emptyCache maxAge maxCount =
  fmap Cache . newTVarIO $ CD Map.empty Map.empty maxAge maxCount

data CacheMetrics = CM { metricPopulation :: Gauge
                       , metricQueries :: Vector Label1 Counter
                       , metricEvictions :: Counter
                       }

data Cache a = Cache (TVar (CacheData a))
             | InstrumentedCache CacheMetrics (TVar (CacheData a))

unwrapCache :: Cache a -> TVar (CacheData a)
unwrapCache (Cache c) = c
unwrapCache (InstrumentedCache _ c) = c

emptyCache :: (Maybe Text) -> NominalDiffTime -> Int -> IO (Cache a)
emptyCache metricPrefix maxAge maxCount = do
  wrap <- case metricPrefix of
            Nothing -> pure Cache
            Just pfx -> do
              pop <- register (gauge $ Info (pfx <> "_population_count") "Current entry count")
              q <- register (vector "result" $ counter $ Info (pfx <> "_queries") "Cache lookups")
              ev <- register (counter $ Info (pfx <> "_eviction_count") "Evicted entries")
              setGauge pop 0
              pure $ InstrumentedCache $ CM pop q ev
  fmap wrap . newTVarIO $ CD Map.empty Map.empty maxAge maxCount

cacheInsert :: Hashable a => a -> Cache a -> IO Id
cacheInsert = cacheInsert' getCurrentTime

cacheInsert' :: Hashable a => IO UTCTime -> a -> Cache a -> IO Id
cacheInsert' getTime x (Cache c) = do
cacheInsert' getTime x cache = do
  let h = hash x
      c = unwrapCache cache
  now <- getTime
  pruneCache now (Cache c)
  pruneCache now cache
  atomically $ do
    cd@CD{dataStore,dataAge} <- readTVar c
    let cd' = case Map.lookup h dataStore of


@@ 64,6 86,7 @@ cacheInsert' getTime x (Cache c) = do
                                 & insertId h now
                     }
    writeTVar c cd'
  reportCachePopulation cache
  pure h

insertId,deleteId :: Id -> UTCTime -> Map UTCTime (Set Id) -> Map UTCTime (Set Id)


@@ 82,10 105,11 @@ cacheLookup :: Id -> Cache a -> IO (Maybe a)
cacheLookup = cacheLookup' getCurrentTime

cacheLookup' :: IO UTCTime -> Id -> Cache a -> IO (Maybe a)
cacheLookup' getTime h (Cache c) = do
cacheLookup' getTime h cache = do
  now <- getTime
  pruneCache now (Cache c)
  atomically $ do
  pruneCache now cache
  let c = unwrapCache cache
  result <- atomically $ do
    cd@CD{dataStore,dataAge} <- readTVar c
    case Map.lookup h dataStore of
      Just (dat, previously) -> do


@@ 96,13 120,26 @@ cacheLookup' getTime h (Cache c) = do
             }
        pure $ Just dat
      Nothing -> pure Nothing
  case cache of
    Cache _ -> pure ()
    InstrumentedCache CM{metricQueries} _ ->
      if isJust result then
        withLabel metricQueries "hit" incCounter
      else
        withLabel metricQueries "miss" incCounter
  pure result

pruneCache :: UTCTime -> Cache a -> IO ()
pruneCache now (Cache c) = go
pruneCache now cache = go
  where
    c = unwrapCache cache
    go = do
      didPrune <- maybePruneOldest
      if didPrune then go else pure ()
      if didPrune then
        reportPrune >> go

      else
        reportCachePopulation cache

    maybePruneOldest = atomically $ do
      cd@CD{dataStore,dataAge,maxDataAge,maxDataCount} <- readTVar c


@@ 120,9 157,20 @@ pruneCache now (Cache c) = go
                 }
            pure True

    reportPrune = case cache of
                    InstrumentedCache CM{metricEvictions} _ ->
                        incCounter metricEvictions
                    _ -> pure ()

reportCachePopulation :: Cache a -> IO ()
reportCachePopulation (InstrumentedCache CM{metricPopulation} c) = do
  CD{dataStore} <- readTVarIO c
  setGauge metricPopulation $ fromIntegral $ Map.size dataStore
reportCachePopulation _ = pure ()

assertCacheSize :: Int -> Cache a -> IO ()
assertCacheSize sz (Cache c) = do
  CD{dataStore,dataAge} <- readTVarIO c
assertCacheSize sz cache = do
  CD{dataStore,dataAge} <- readTVarIO $ unwrapCache cache
  when (Map.size dataStore /= sz) $
    throwIO (AssertionFailed $ "Expected dataStore of size " <> show sz <> " but got size " <> show (Map.size dataStore))
  when ( ( dataStore

M src/LogParser/Server.hs => src/LogParser/Server.hs +17 -2
@@ 15,11 15,15 @@ import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.IO as LTIO
import Data.Time.Clock
import GHC.Stats
import Network.HTTP.Types.Status
import Network.Wai (Application)
import qualified Network.Wai.Middleware.Prometheus as Prom
import qualified Network.Wai.Handler.Warp as Warp
import Pipes
import qualified Pipes.Prelude as P
import qualified Prometheus as Prom
import qualified Prometheus.Metric.GHC as Prom
import System.Environment
import Text.Blaze.Html (Html)
import Text.Blaze.Html.Renderer.Text (renderHtml)


@@ 46,14 50,20 @@ runApp = do

app :: (S.ScottyM () -> IO a) -> IO a
app run = do
  -- enable GHC Prometheus metrics
  rtsStatsEnabled <- liftIO getRTSStatsEnabled
  when rtsStatsEnabled $ Prom.register Prom.ghcMetrics >> pure ()

  -- initialize the game cache
  cacheMaxAge <- (nominalDay *)
                 . fromMaybe 3
                 . fmap secondsToNominalDiffTime
                 . (readMaybe =<<)
                 <$> lookupEnv "DATA_CACHE_DURATION_DAYS"
  cacheMaxSize <- fromMaybe 1000 . (readMaybe =<<) <$> lookupEnv "DATA_CACHE_SIZE"
  cache <- emptyCache cacheMaxAge cacheMaxSize
  run $ routes cache
  cache <- emptyCache (Just "logparser_game_cache") cacheMaxAge cacheMaxSize

  run $ middlewares >> routes cache

routes :: Cache TidyDataCollection -> S.ScottyM ()
routes cache = do


@@ 166,3 176,8 @@ testList =
#else
  []
#endif


middlewares :: S.ScottyM ()
middlewares = do
  S.middleware $ Prom.prometheus Prom.def

M stack.yaml => stack.yaml +3 -0
@@ 42,6 42,9 @@ packages:
# extra-deps: []
extra-deps:
- base32-0.2.0.0
- wai-middleware-prometheus-1.0.0
- prometheus-metrics-ghc-1.0.1.1
- prometheus-client-1.0.1

# Override default flag values for local packages and extra-deps
# flags: {}

M stack.yaml.lock => stack.yaml.lock +21 -0
@@ 11,6 11,27 @@ packages:
      sha256: 10c0a5a0a1d4c40b41f0190cf80b114fb527caf7458feec819d87ccfe41317cb
  original:
    hackage: base32-0.2.0.0
- completed:
    hackage: wai-middleware-prometheus-1.0.0@sha256:1625792914fb2139f005685be8ce519111451cfb854816e430fbf54af46238b4,1314
    pantry-tree:
      size: 307
      sha256: 6d64803c639ed4c7204ea6fab0536b97d3ee16cdecb9b4a883cd8e56d3c61402
  original:
    hackage: wai-middleware-prometheus-1.0.0
- completed:
    hackage: prometheus-metrics-ghc-1.0.1.1@sha256:d378a7186a967140fe0e09d325fe5e3bfd7b77a1123934b40f81fdfed2eacbdc,1233
    pantry-tree:
      size: 293
      sha256: 0732085a4148b269bbc15eeb7ab422e65ac287878a42a7388a7b6e140ec740e5
  original:
    hackage: prometheus-metrics-ghc-1.0.1.1
- completed:
    hackage: prometheus-client-1.0.1@sha256:eef0399a1b4296624c7cde11c72d93ec53329a92b1a2f6cbdceeeff2b8c20308,2518
    pantry-tree:
      size: 1225
      sha256: 4e9cf0b88a280d2053ed72bfea6f47ffa9b1eaa27422ef171011b77ccef28fdf
  original:
    hackage: prometheus-client-1.0.1
snapshots:
- completed:
    size: 533053

M test/CacheSpec.hs => test/CacheSpec.hs +4 -4
@@ 20,7 20,7 @@ spec = do
  describe "cache" $ do
    it "retrieves items" $
      ( do
          c <- emptyCache nominalDay 1
          c <- emptyCache Nothing nominalDay 1
          i <- cacheInsert' now "foo" c
          cacheLookup' now i c
            <* assertCacheSize 1 c


@@ 30,7 30,7 @@ spec = do

    it "prunes based on size" $
      ( do
          c <- emptyCache nominalDay 2
          c <- emptyCache Nothing nominalDay 2
          i1 <- cacheInsert' now "foo" c
          i2 <- cacheInsert' (nowPlus 1)  "bar" c
          i3 <- cacheInsert' (nowPlus 2) "baz" c


@@ 42,7 42,7 @@ spec = do

    it "prunes based on time" $
      ( do
          c <- emptyCache (secondsToNominalDiffTime 3) 5
          c <- emptyCache Nothing (secondsToNominalDiffTime 3) 5
          i1 <- cacheInsert' now "foo" c
          i2 <- cacheInsert' (nowPlus 2) "bar" c
          mapM (\i -> cacheLookup' (nowPlus 4) i c) [i1, i2]


@@ 53,7 53,7 @@ spec = do

    it "refreshes the timeout" $
      ( do
          c <- emptyCache (secondsToNominalDiffTime 4) 5
          c <- emptyCache Nothing (secondsToNominalDiffTime 4) 5
          i1 <- cacheInsert' now "foo" c
          i2 <- cacheInsert' (nowPlus 1) "bar" c
          r1 <- cacheLookup' (nowPlus 3) i1 c