-- Copyright (c) 2014-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is distributed under the terms of a BSD license,
-- found in the LICENSE file.

{-# LANGUAGE BangPatterns #-}

-- |
-- The 'Flags' type and related functions.  This module is provided
-- for access to Haxl internals only; most users should import
-- "Haxl.Core" instead.
--
module Haxl.Core.Flags
  (
    -- * Report flags
    ReportFlag(..)
  , ReportFlags
  , defaultReportFlags
  , profilingReportFlags
  , setReportFlag
  , clearReportFlag
  , testReportFlag
    -- * Flags
  , Flags(..)
  , defaultFlags
  , ifTrace
  , ifReport
  , ifProfiling
  ) where

import Control.Monad
import Data.Bits
import Data.List (foldl')
import Text.Printf (printf)

-- ---------------------------------------------------------------------------
-- ReportFlags
data ReportFlag
  = ReportOutgoneFetches  -- ^ outgone fetches, for debugging eg: timeouts
  | ReportFetchStats  -- ^ data fetch stats & errors
  | ReportProfiling   -- ^ enabling label stack and profiling
  | ReportExceptionLabelStack  -- ^ include label stack in HaxlException
  | ReportFetchStack  -- ^ log cost-center stack traces of dataFetch calls
  deriving (ReportFlag
ReportFlag -> ReportFlag -> Bounded ReportFlag
forall a. a -> a -> Bounded a
maxBound :: ReportFlag
$cmaxBound :: ReportFlag
minBound :: ReportFlag
$cminBound :: ReportFlag
Bounded, Int -> ReportFlag
ReportFlag -> Int
ReportFlag -> [ReportFlag]
ReportFlag -> ReportFlag
ReportFlag -> ReportFlag -> [ReportFlag]
ReportFlag -> ReportFlag -> ReportFlag -> [ReportFlag]
(ReportFlag -> ReportFlag)
-> (ReportFlag -> ReportFlag)
-> (Int -> ReportFlag)
-> (ReportFlag -> Int)
-> (ReportFlag -> [ReportFlag])
-> (ReportFlag -> ReportFlag -> [ReportFlag])
-> (ReportFlag -> ReportFlag -> [ReportFlag])
-> (ReportFlag -> ReportFlag -> ReportFlag -> [ReportFlag])
-> Enum ReportFlag
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
enumFromThenTo :: ReportFlag -> ReportFlag -> ReportFlag -> [ReportFlag]
$cenumFromThenTo :: ReportFlag -> ReportFlag -> ReportFlag -> [ReportFlag]
enumFromTo :: ReportFlag -> ReportFlag -> [ReportFlag]
$cenumFromTo :: ReportFlag -> ReportFlag -> [ReportFlag]
enumFromThen :: ReportFlag -> ReportFlag -> [ReportFlag]
$cenumFromThen :: ReportFlag -> ReportFlag -> [ReportFlag]
enumFrom :: ReportFlag -> [ReportFlag]
$cenumFrom :: ReportFlag -> [ReportFlag]
fromEnum :: ReportFlag -> Int
$cfromEnum :: ReportFlag -> Int
toEnum :: Int -> ReportFlag
$ctoEnum :: Int -> ReportFlag
pred :: ReportFlag -> ReportFlag
$cpred :: ReportFlag -> ReportFlag
succ :: ReportFlag -> ReportFlag
$csucc :: ReportFlag -> ReportFlag
Enum, ReportFlag -> ReportFlag -> Bool
(ReportFlag -> ReportFlag -> Bool)
-> (ReportFlag -> ReportFlag -> Bool) -> Eq ReportFlag
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ReportFlag -> ReportFlag -> Bool
$c/= :: ReportFlag -> ReportFlag -> Bool
== :: ReportFlag -> ReportFlag -> Bool
$c== :: ReportFlag -> ReportFlag -> Bool
Eq, Int -> ReportFlag -> ShowS
[ReportFlag] -> ShowS
ReportFlag -> String
(Int -> ReportFlag -> ShowS)
-> (ReportFlag -> String)
-> ([ReportFlag] -> ShowS)
-> Show ReportFlag
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ReportFlag] -> ShowS
$cshowList :: [ReportFlag] -> ShowS
show :: ReportFlag -> String
$cshow :: ReportFlag -> String
showsPrec :: Int -> ReportFlag -> ShowS
$cshowsPrec :: Int -> ReportFlag -> ShowS
Show)

profilingDependents :: [ReportFlag]
profilingDependents :: [ReportFlag]
profilingDependents =
  [ ReportFlag
ReportExceptionLabelStack
  , ReportFlag
ReportFetchStack
  ]

newtype ReportFlags = ReportFlags Int

instance Show ReportFlags where
  show :: ReportFlags -> String
show (ReportFlags Int
fs) = String -> Int -> Int -> String
forall r. PrintfType r => String -> r
printf String
"%0*b" (ReportFlag -> Int
forall a. Enum a => a -> Int
fromEnum ReportFlag
maxReportFlag Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) Int
fs
    where
      maxReportFlag :: ReportFlag
maxReportFlag = ReportFlag
forall a. Bounded a => a
maxBound :: ReportFlag

defaultReportFlags :: ReportFlags
defaultReportFlags :: ReportFlags
defaultReportFlags = Int -> ReportFlags
ReportFlags Int
0

profilingReportFlags :: ReportFlags
profilingReportFlags :: ReportFlags
profilingReportFlags = (ReportFlags -> ReportFlag -> ReportFlags)
-> ReportFlags -> [ReportFlag] -> ReportFlags
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' ((ReportFlag -> ReportFlags -> ReportFlags)
-> ReportFlags -> ReportFlag -> ReportFlags
forall a b c. (a -> b -> c) -> b -> a -> c
flip ReportFlag -> ReportFlags -> ReportFlags
setReportFlag) ReportFlags
defaultReportFlags
  [ ReportFlag
ReportOutgoneFetches
  , ReportFlag
ReportFetchStats
  , ReportFlag
ReportProfiling
  ]

setReportFlag :: ReportFlag -> ReportFlags -> ReportFlags
setReportFlag :: ReportFlag -> ReportFlags -> ReportFlags
setReportFlag ReportFlag
f (ReportFlags Int
fs) =
  Int -> ReportFlags
ReportFlags (Int -> ReportFlags) -> Int -> ReportFlags
forall a b. (a -> b) -> a -> b
$ Int -> Int
setDependencies (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ Int -> Int -> Int
forall a. Bits a => a -> Int -> a
setBit Int
fs (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ ReportFlag -> Int
forall a. Enum a => a -> Int
fromEnum ReportFlag
f
  where
    setDependencies :: Int -> Int
setDependencies
      | ReportFlag
f ReportFlag -> [ReportFlag] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ReportFlag]
profilingDependents = (Int -> Int -> Int) -> Int -> Int -> Int
forall a b c. (a -> b -> c) -> b -> a -> c
flip Int -> Int -> Int
forall a. Bits a => a -> Int -> a
setBit (Int -> Int -> Int) -> Int -> Int -> Int
forall a b. (a -> b) -> a -> b
$ ReportFlag -> Int
forall a. Enum a => a -> Int
fromEnum ReportFlag
ReportProfiling
      | Bool
otherwise = Int -> Int
forall a. a -> a
id

clearReportFlag :: ReportFlag -> ReportFlags -> ReportFlags
clearReportFlag :: ReportFlag -> ReportFlags -> ReportFlags
clearReportFlag ReportFlag
f (ReportFlags Int
fs) =
  Int -> ReportFlags
ReportFlags (Int -> ReportFlags) -> Int -> ReportFlags
forall a b. (a -> b) -> a -> b
$ Int -> Int
forall p. Bits p => p -> p
clearDependents (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ Int -> Int -> Int
forall a. Bits a => a -> Int -> a
clearBit Int
fs (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ ReportFlag -> Int
forall a. Enum a => a -> Int
fromEnum ReportFlag
f
  where
    clearDependents :: p -> p
clearDependents p
z = case ReportFlag
f of
      ReportFlag
ReportProfiling -> (p -> Int -> p) -> p -> [Int] -> p
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' p -> Int -> p
forall a. Bits a => a -> Int -> a
clearBit p
z ([Int] -> p) -> [Int] -> p
forall a b. (a -> b) -> a -> b
$ (ReportFlag -> Int) -> [ReportFlag] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map ReportFlag -> Int
forall a. Enum a => a -> Int
fromEnum [ReportFlag]
profilingDependents
      ReportFlag
_ -> p
z

{-# INLINE testReportFlag #-}
testReportFlag :: ReportFlag -> ReportFlags -> Bool
testReportFlag :: ReportFlag -> ReportFlags -> Bool
testReportFlag !ReportFlag
f (ReportFlags !Int
fs) = Int -> Int -> Bool
forall a. Bits a => a -> Int -> Bool
testBit Int
fs (Int -> Bool) -> Int -> Bool
forall a b. (a -> b) -> a -> b
$ ReportFlag -> Int
forall a. Enum a => a -> Int
fromEnum ReportFlag
f

-- ---------------------------------------------------------------------------
-- Flags

-- | Flags that control the operation of the engine.
data Flags = Flags
  { Flags -> Int
trace :: {-# UNPACK #-} !Int
    -- ^ Tracing level (0 = quiet, 3 = very verbose).
  , Flags -> ReportFlags
report :: {-# UNPACK #-} !ReportFlags
    -- ^ Report flags
  , Flags -> Int
caching :: {-# UNPACK #-} !Int
    -- ^ Non-zero if caching is enabled.  If caching is disabled, then
    -- we still do batching and de-duplication, but do not cache
    -- results.
  , Flags -> Int
recording :: {-# UNPACK #-} !Int
    -- ^ Non-zero if recording is enabled. This allows tests to record cache
    -- calls for datasources by making uncachedRequest behave like dataFetch
  }

defaultFlags :: Flags
defaultFlags :: Flags
defaultFlags = Flags :: Int -> ReportFlags -> Int -> Int -> Flags
Flags
  { trace :: Int
trace = Int
0
  , report :: ReportFlags
report = ReportFlags
defaultReportFlags
  , caching :: Int
caching = Int
1
  , recording :: Int
recording = Int
0
  }

-- | Runs an action if the tracing level is above the given threshold.
ifTrace :: Monad m => Flags -> Int -> m a -> m ()
ifTrace :: Flags -> Int -> m a -> m ()
ifTrace Flags
flags Int
i = Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Flags -> Int
trace Flags
flags Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
i) (m () -> m ()) -> (m a -> m ()) -> m a -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. m a -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void

-- | Runs an action if the ReportFlag is set.
ifReport :: Monad m => Flags -> ReportFlag -> m a -> m ()
ifReport :: Flags -> ReportFlag -> m a -> m ()
ifReport Flags
flags ReportFlag
i = Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (ReportFlag -> ReportFlags -> Bool
testReportFlag ReportFlag
i (ReportFlags -> Bool) -> ReportFlags -> Bool
forall a b. (a -> b) -> a -> b
$ Flags -> ReportFlags
report Flags
flags) (m () -> m ()) -> (m a -> m ()) -> m a -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. m a -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void

ifProfiling :: Monad m => Flags -> m a -> m ()
ifProfiling :: Flags -> m a -> m ()
ifProfiling Flags
flags = Flags -> ReportFlag -> m a -> m ()
forall (m :: * -> *) a.
Monad m =>
Flags -> ReportFlag -> m a -> m ()
ifReport Flags
flags ReportFlag
ReportProfiling