{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Postgres health snapshots from the stats and catalog views. The queries name Arbiter's
-- own tables and do not scan them.
module Arbiter.Core.Health
  ( PgDbHealth (..)
  , PgTableHealth (..)
  , getPgHealth
  , getPgDbHealth
  ) where

import Data.Aeson (FromJSON, ToJSON)
import Data.Int (Int64)
import Data.Maybe (listToMaybe)
import Data.Text (Text)
import GHC.Generics (Generic)

import Arbiter.Core.Codec (Col (..), RowCodec, col, ncol)
import Arbiter.Core.Job.Schema (SchemaName, TableName)
import Arbiter.Core.MonadArbiter (MonadArbiter (..))
import Arbiter.Core.SchemaTables (allSchemaTables)
import Arbiter.Core.Sql.Health qualified as Sql
import Arbiter.Core.Sql.Query (rows)

-- | Connection and age counters for the current database, shared with its other clients.
data PgDbHealth = PgDbHealth
  { PgDbHealth -> Int64
numBackends :: Int64
  , PgDbHealth -> Int64
connActive :: Int64
  , PgDbHealth -> Int64
connIdle :: Int64
  , PgDbHealth -> Int64
connIdleInTxn :: Int64
  , PgDbHealth -> Int64
connIdleInTxnAborted :: Int64
  , PgDbHealth -> Int64
connBlocked :: Int64
  , PgDbHealth -> Int64
connOther :: Int64
  , PgDbHealth -> Double
oldestTxnAge :: Double
  , PgDbHealth -> Double
oldestQueryAge :: Double
  }
  deriving stock (PgDbHealth -> PgDbHealth -> Bool
(PgDbHealth -> PgDbHealth -> Bool)
-> (PgDbHealth -> PgDbHealth -> Bool) -> Eq PgDbHealth
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PgDbHealth -> PgDbHealth -> Bool
== :: PgDbHealth -> PgDbHealth -> Bool
$c/= :: PgDbHealth -> PgDbHealth -> Bool
/= :: PgDbHealth -> PgDbHealth -> Bool
Eq, (forall x. PgDbHealth -> Rep PgDbHealth x)
-> (forall x. Rep PgDbHealth x -> PgDbHealth) -> Generic PgDbHealth
forall x. Rep PgDbHealth x -> PgDbHealth
forall x. PgDbHealth -> Rep PgDbHealth x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. PgDbHealth -> Rep PgDbHealth x
from :: forall x. PgDbHealth -> Rep PgDbHealth x
$cto :: forall x. Rep PgDbHealth x -> PgDbHealth
to :: forall x. Rep PgDbHealth x -> PgDbHealth
Generic, Int -> PgDbHealth -> ShowS
[PgDbHealth] -> ShowS
PgDbHealth -> String
(Int -> PgDbHealth -> ShowS)
-> (PgDbHealth -> String)
-> ([PgDbHealth] -> ShowS)
-> Show PgDbHealth
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PgDbHealth -> ShowS
showsPrec :: Int -> PgDbHealth -> ShowS
$cshow :: PgDbHealth -> String
show :: PgDbHealth -> String
$cshowList :: [PgDbHealth] -> ShowS
showList :: [PgDbHealth] -> ShowS
Show)
  deriving anyclass (Maybe PgDbHealth
Value -> Parser [PgDbHealth]
Value -> Parser PgDbHealth
(Value -> Parser PgDbHealth)
-> (Value -> Parser [PgDbHealth])
-> Maybe PgDbHealth
-> FromJSON PgDbHealth
forall a.
(Value -> Parser a)
-> (Value -> Parser [a]) -> Maybe a -> FromJSON a
$cparseJSON :: Value -> Parser PgDbHealth
parseJSON :: Value -> Parser PgDbHealth
$cparseJSONList :: Value -> Parser [PgDbHealth]
parseJSONList :: Value -> Parser [PgDbHealth]
$comittedField :: Maybe PgDbHealth
omittedField :: Maybe PgDbHealth
FromJSON, [PgDbHealth] -> Value
[PgDbHealth] -> Encoding
PgDbHealth -> Bool
PgDbHealth -> Value
PgDbHealth -> Encoding
(PgDbHealth -> Value)
-> (PgDbHealth -> Encoding)
-> ([PgDbHealth] -> Value)
-> ([PgDbHealth] -> Encoding)
-> (PgDbHealth -> Bool)
-> ToJSON PgDbHealth
forall a.
(a -> Value)
-> (a -> Encoding)
-> ([a] -> Value)
-> ([a] -> Encoding)
-> (a -> Bool)
-> ToJSON a
$ctoJSON :: PgDbHealth -> Value
toJSON :: PgDbHealth -> Value
$ctoEncoding :: PgDbHealth -> Encoding
toEncoding :: PgDbHealth -> Encoding
$ctoJSONList :: [PgDbHealth] -> Value
toJSONList :: [PgDbHealth] -> Value
$ctoEncodingList :: [PgDbHealth] -> Encoding
toEncodingList :: [PgDbHealth] -> Encoding
$comitField :: PgDbHealth -> Bool
omitField :: PgDbHealth -> Bool
ToJSON)

-- | Per-table tuple counts, size, scan counters, block traffic, and freeze age.
data PgTableHealth = PgTableHealth
  { PgTableHealth -> Text
table :: Text
  , PgTableHealth -> Int64
liveTup :: Int64
  , PgTableHealth -> Int64
deadTup :: Int64
  , PgTableHealth -> Maybe Double
autovacuumAge :: Maybe Double
  -- ^ Nothing when the table has never been vacuumed.
  , PgTableHealth -> Int64
totalBytes :: Int64
  , PgTableHealth -> Double
seqScan :: Double
  , PgTableHealth -> Double
idxScan :: Double
  , PgTableHealth -> Double
blksHit :: Double
  , PgTableHealth -> Double
blksRead :: Double
  , PgTableHealth -> Maybe Int64
xidAge :: Maybe Int64
  -- ^ Nothing for a relation with no frozen transaction id of its own.
  }
  deriving stock (PgTableHealth -> PgTableHealth -> Bool
(PgTableHealth -> PgTableHealth -> Bool)
-> (PgTableHealth -> PgTableHealth -> Bool) -> Eq PgTableHealth
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PgTableHealth -> PgTableHealth -> Bool
== :: PgTableHealth -> PgTableHealth -> Bool
$c/= :: PgTableHealth -> PgTableHealth -> Bool
/= :: PgTableHealth -> PgTableHealth -> Bool
Eq, (forall x. PgTableHealth -> Rep PgTableHealth x)
-> (forall x. Rep PgTableHealth x -> PgTableHealth)
-> Generic PgTableHealth
forall x. Rep PgTableHealth x -> PgTableHealth
forall x. PgTableHealth -> Rep PgTableHealth x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. PgTableHealth -> Rep PgTableHealth x
from :: forall x. PgTableHealth -> Rep PgTableHealth x
$cto :: forall x. Rep PgTableHealth x -> PgTableHealth
to :: forall x. Rep PgTableHealth x -> PgTableHealth
Generic, Int -> PgTableHealth -> ShowS
[PgTableHealth] -> ShowS
PgTableHealth -> String
(Int -> PgTableHealth -> ShowS)
-> (PgTableHealth -> String)
-> ([PgTableHealth] -> ShowS)
-> Show PgTableHealth
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PgTableHealth -> ShowS
showsPrec :: Int -> PgTableHealth -> ShowS
$cshow :: PgTableHealth -> String
show :: PgTableHealth -> String
$cshowList :: [PgTableHealth] -> ShowS
showList :: [PgTableHealth] -> ShowS
Show)
  deriving anyclass (Maybe PgTableHealth
Value -> Parser [PgTableHealth]
Value -> Parser PgTableHealth
(Value -> Parser PgTableHealth)
-> (Value -> Parser [PgTableHealth])
-> Maybe PgTableHealth
-> FromJSON PgTableHealth
forall a.
(Value -> Parser a)
-> (Value -> Parser [a]) -> Maybe a -> FromJSON a
$cparseJSON :: Value -> Parser PgTableHealth
parseJSON :: Value -> Parser PgTableHealth
$cparseJSONList :: Value -> Parser [PgTableHealth]
parseJSONList :: Value -> Parser [PgTableHealth]
$comittedField :: Maybe PgTableHealth
omittedField :: Maybe PgTableHealth
FromJSON, [PgTableHealth] -> Value
[PgTableHealth] -> Encoding
PgTableHealth -> Bool
PgTableHealth -> Value
PgTableHealth -> Encoding
(PgTableHealth -> Value)
-> (PgTableHealth -> Encoding)
-> ([PgTableHealth] -> Value)
-> ([PgTableHealth] -> Encoding)
-> (PgTableHealth -> Bool)
-> ToJSON PgTableHealth
forall a.
(a -> Value)
-> (a -> Encoding)
-> ([a] -> Value)
-> ([a] -> Encoding)
-> (a -> Bool)
-> ToJSON a
$ctoJSON :: PgTableHealth -> Value
toJSON :: PgTableHealth -> Value
$ctoEncoding :: PgTableHealth -> Encoding
toEncoding :: PgTableHealth -> Encoding
$ctoJSONList :: [PgTableHealth] -> Value
toJSONList :: [PgTableHealth] -> Value
$ctoEncodingList :: [PgTableHealth] -> Encoding
toEncodingList :: [PgTableHealth] -> Encoding
$comitField :: PgTableHealth -> Bool
omitField :: PgTableHealth -> Bool
ToJSON)

-- | Column order matches the SELECT lists. 'RowCodec' is positional.
pgDbHealthCodec :: RowCodec PgDbHealth
pgDbHealthCodec :: RowCodec PgDbHealth
pgDbHealthCodec =
  Int64
-> Int64
-> Int64
-> Int64
-> Int64
-> Int64
-> Int64
-> Double
-> Double
-> PgDbHealth
PgDbHealth
    (Int64
 -> Int64
 -> Int64
 -> Int64
 -> Int64
 -> Int64
 -> Int64
 -> Double
 -> Double
 -> PgDbHealth)
-> Ap NullCol Int64
-> Ap
     NullCol
     (Int64
      -> Int64
      -> Int64
      -> Int64
      -> Int64
      -> Int64
      -> Double
      -> Double
      -> PgDbHealth)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Col Int64 -> Ap NullCol Int64
forall a. Text -> Col a -> RowCodec a
col Text
"numbackends" Col Int64
CInt8
    Ap
  NullCol
  (Int64
   -> Int64
   -> Int64
   -> Int64
   -> Int64
   -> Int64
   -> Double
   -> Double
   -> PgDbHealth)
-> Ap NullCol Int64
-> Ap
     NullCol
     (Int64
      -> Int64
      -> Int64
      -> Int64
      -> Int64
      -> Double
      -> Double
      -> PgDbHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Int64 -> Ap NullCol Int64
forall a. Text -> Col a -> RowCodec a
col Text
"active" Col Int64
CInt8
    Ap
  NullCol
  (Int64
   -> Int64
   -> Int64
   -> Int64
   -> Int64
   -> Double
   -> Double
   -> PgDbHealth)
-> Ap NullCol Int64
-> Ap
     NullCol
     (Int64
      -> Int64 -> Int64 -> Int64 -> Double -> Double -> PgDbHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Int64 -> Ap NullCol Int64
forall a. Text -> Col a -> RowCodec a
col Text
"idle" Col Int64
CInt8
    Ap
  NullCol
  (Int64
   -> Int64 -> Int64 -> Int64 -> Double -> Double -> PgDbHealth)
-> Ap NullCol Int64
-> Ap
     NullCol (Int64 -> Int64 -> Int64 -> Double -> Double -> PgDbHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Int64 -> Ap NullCol Int64
forall a. Text -> Col a -> RowCodec a
col Text
"idle_in_txn" Col Int64
CInt8
    Ap
  NullCol (Int64 -> Int64 -> Int64 -> Double -> Double -> PgDbHealth)
-> Ap NullCol Int64
-> Ap NullCol (Int64 -> Int64 -> Double -> Double -> PgDbHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Int64 -> Ap NullCol Int64
forall a. Text -> Col a -> RowCodec a
col Text
"idle_in_txn_aborted" Col Int64
CInt8
    Ap NullCol (Int64 -> Int64 -> Double -> Double -> PgDbHealth)
-> Ap NullCol Int64
-> Ap NullCol (Int64 -> Double -> Double -> PgDbHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Int64 -> Ap NullCol Int64
forall a. Text -> Col a -> RowCodec a
col Text
"blocked" Col Int64
CInt8
    Ap NullCol (Int64 -> Double -> Double -> PgDbHealth)
-> Ap NullCol Int64 -> Ap NullCol (Double -> Double -> PgDbHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Int64 -> Ap NullCol Int64
forall a. Text -> Col a -> RowCodec a
col Text
"other" Col Int64
CInt8
    Ap NullCol (Double -> Double -> PgDbHealth)
-> Ap NullCol Double -> Ap NullCol (Double -> PgDbHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Double -> Ap NullCol Double
forall a. Text -> Col a -> RowCodec a
col Text
"oldest_txn_age" Col Double
CFloat8
    Ap NullCol (Double -> PgDbHealth)
-> Ap NullCol Double -> RowCodec PgDbHealth
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Double -> Ap NullCol Double
forall a. Text -> Col a -> RowCodec a
col Text
"oldest_query_age" Col Double
CFloat8

pgTableHealthCodec :: RowCodec PgTableHealth
pgTableHealthCodec :: RowCodec PgTableHealth
pgTableHealthCodec =
  Text
-> Int64
-> Int64
-> Maybe Double
-> Int64
-> Double
-> Double
-> Double
-> Double
-> Maybe Int64
-> PgTableHealth
PgTableHealth
    (Text
 -> Int64
 -> Int64
 -> Maybe Double
 -> Int64
 -> Double
 -> Double
 -> Double
 -> Double
 -> Maybe Int64
 -> PgTableHealth)
-> Ap NullCol Text
-> Ap
     NullCol
     (Int64
      -> Int64
      -> Maybe Double
      -> Int64
      -> Double
      -> Double
      -> Double
      -> Double
      -> Maybe Int64
      -> PgTableHealth)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Col Text -> Ap NullCol Text
forall a. Text -> Col a -> RowCodec a
col Text
"relname" Col Text
CText
    Ap
  NullCol
  (Int64
   -> Int64
   -> Maybe Double
   -> Int64
   -> Double
   -> Double
   -> Double
   -> Double
   -> Maybe Int64
   -> PgTableHealth)
-> Ap NullCol Int64
-> Ap
     NullCol
     (Int64
      -> Maybe Double
      -> Int64
      -> Double
      -> Double
      -> Double
      -> Double
      -> Maybe Int64
      -> PgTableHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Int64 -> Ap NullCol Int64
forall a. Text -> Col a -> RowCodec a
col Text
"n_live_tup" Col Int64
CInt8
    Ap
  NullCol
  (Int64
   -> Maybe Double
   -> Int64
   -> Double
   -> Double
   -> Double
   -> Double
   -> Maybe Int64
   -> PgTableHealth)
-> Ap NullCol Int64
-> Ap
     NullCol
     (Maybe Double
      -> Int64
      -> Double
      -> Double
      -> Double
      -> Double
      -> Maybe Int64
      -> PgTableHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Int64 -> Ap NullCol Int64
forall a. Text -> Col a -> RowCodec a
col Text
"n_dead_tup" Col Int64
CInt8
    Ap
  NullCol
  (Maybe Double
   -> Int64
   -> Double
   -> Double
   -> Double
   -> Double
   -> Maybe Int64
   -> PgTableHealth)
-> Ap NullCol (Maybe Double)
-> Ap
     NullCol
     (Int64
      -> Double
      -> Double
      -> Double
      -> Double
      -> Maybe Int64
      -> PgTableHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Double -> Ap NullCol (Maybe Double)
forall a. Text -> Col a -> RowCodec (Maybe a)
ncol Text
"autovacuum_age" Col Double
CFloat8
    Ap
  NullCol
  (Int64
   -> Double
   -> Double
   -> Double
   -> Double
   -> Maybe Int64
   -> PgTableHealth)
-> Ap NullCol Int64
-> Ap
     NullCol
     (Double
      -> Double -> Double -> Double -> Maybe Int64 -> PgTableHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Int64 -> Ap NullCol Int64
forall a. Text -> Col a -> RowCodec a
col Text
"total_bytes" Col Int64
CInt8
    Ap
  NullCol
  (Double
   -> Double -> Double -> Double -> Maybe Int64 -> PgTableHealth)
-> Ap NullCol Double
-> Ap
     NullCol
     (Double -> Double -> Double -> Maybe Int64 -> PgTableHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Double -> Ap NullCol Double
forall a. Text -> Col a -> RowCodec a
col Text
"seq_scan" Col Double
CFloat8
    Ap
  NullCol
  (Double -> Double -> Double -> Maybe Int64 -> PgTableHealth)
-> Ap NullCol Double
-> Ap NullCol (Double -> Double -> Maybe Int64 -> PgTableHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Double -> Ap NullCol Double
forall a. Text -> Col a -> RowCodec a
col Text
"idx_scan" Col Double
CFloat8
    Ap NullCol (Double -> Double -> Maybe Int64 -> PgTableHealth)
-> Ap NullCol Double
-> Ap NullCol (Double -> Maybe Int64 -> PgTableHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Double -> Ap NullCol Double
forall a. Text -> Col a -> RowCodec a
col Text
"blks_hit" Col Double
CFloat8
    Ap NullCol (Double -> Maybe Int64 -> PgTableHealth)
-> Ap NullCol Double -> Ap NullCol (Maybe Int64 -> PgTableHealth)
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Double -> Ap NullCol Double
forall a. Text -> Col a -> RowCodec a
col Text
"blks_read" Col Double
CFloat8
    Ap NullCol (Maybe Int64 -> PgTableHealth)
-> Ap NullCol (Maybe Int64) -> RowCodec PgTableHealth
forall a b. Ap NullCol (a -> b) -> Ap NullCol a -> Ap NullCol b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> Col Int64 -> Ap NullCol (Maybe Int64)
forall a. Text -> Col a -> RowCodec (Maybe a)
ncol Text
"xid_age" Col Int64
CInt8

-- | Database-wide health and per-table churn for the given queues' tables and the schema's
-- shared arbiter tables. Backends owned by another role report their state as unknown,
-- absent @pg_read_all_stats@.
getPgHealth :: (MonadArbiter m) => SchemaName -> [TableName] -> m (Maybe PgDbHealth, [PgTableHealth])
getPgHealth :: forall (m :: * -> *).
MonadArbiter m =>
Text -> [Text] -> m (Maybe PgDbHealth, [PgTableHealth])
getPgHealth Text
schemaName [Text]
queueTables = do
  dbHealth <- m (Maybe PgDbHealth)
forall (m :: * -> *). MonadArbiter m => m (Maybe PgDbHealth)
getPgDbHealth
  tableRows <- executeQuery (rows pgTableHealthCodec (Sql.pgTableHealthSQL schemaName scanned))
  pure (dbHealth, tableRows)
  where
    scanned :: [Text]
scanned = [Text] -> [Text]
allSchemaTables [Text]
queueTables

-- | The database-wide half on its own.
getPgDbHealth :: (MonadArbiter m) => m (Maybe PgDbHealth)
getPgDbHealth :: forall (m :: * -> *). MonadArbiter m => m (Maybe PgDbHealth)
getPgDbHealth = [PgDbHealth] -> Maybe PgDbHealth
forall a. [a] -> Maybe a
listToMaybe ([PgDbHealth] -> Maybe PgDbHealth)
-> m [PgDbHealth] -> m (Maybe PgDbHealth)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Query PgDbHealth -> m [PgDbHealth]
forall a. Query a -> m [a]
forall (m :: * -> *) a. MonadArbiter m => Query a -> m [a]
executeQuery (RowCodec PgDbHealth -> Query () -> Query PgDbHealth
forall a. RowCodec a -> Query () -> Query a
rows RowCodec PgDbHealth
pgDbHealthCodec Query ()
Sql.pgDbHealthSQL)