{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module Ouroboros.Consensus.Util.LeakyBucket (
Config (..)
, Handlers (..)
, State (..)
, atomicallyWithMonotonicTime
, diffTimeToSecondsRational
, dummyConfig
, evalAgainstBucket
, execAgainstBucket
, execAgainstBucket'
, fill'
, microsecondsPerSecond
, picosecondsPerSecond
, runAgainstBucket
, secondsRationalToDiffTime
, setPaused'
, updateConfig'
) where
import Control.Exception (assert)
import Control.Monad (forever, void, when)
import qualified Control.Monad.Class.MonadSTM.Internal as TVar
import Control.Monad.Class.MonadTimer (MonadTimer, registerDelay)
import Control.Monad.Class.MonadTimer.SI (diffTimeToMicrosecondsAsInt)
import Data.Ratio ((%))
import Data.Time.Clock (diffTimeToPicoseconds)
import GHC.Generics (Generic)
import Ouroboros.Consensus.Util.IOLike hiding (killThread)
import Ouroboros.Consensus.Util.STM (blockUntilChanged)
import Prelude hiding (init)
data Config m = Config
{
forall (m :: * -> *). Config m -> Rational
capacity :: !Rational,
forall (m :: * -> *). Config m -> Rational
rate :: !Rational,
forall (m :: * -> *). Config m -> Bool
fillOnOverflow :: !Bool,
forall (m :: * -> *). Config m -> m ()
onEmpty :: !(m ())
}
deriving ((forall x. Config m -> Rep (Config m) x)
-> (forall x. Rep (Config m) x -> Config m) -> Generic (Config m)
forall x. Rep (Config m) x -> Config m
forall x. Config m -> Rep (Config m) x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall (m :: * -> *) x. Rep (Config m) x -> Config m
forall (m :: * -> *) x. Config m -> Rep (Config m) x
$cfrom :: forall (m :: * -> *) x. Config m -> Rep (Config m) x
from :: forall x. Config m -> Rep (Config m) x
$cto :: forall (m :: * -> *) x. Rep (Config m) x -> Config m
to :: forall x. Rep (Config m) x -> Config m
Generic)
deriving instance NoThunks (m ()) => NoThunks (Config m)
dummyConfig :: (Applicative m) => Config m
dummyConfig :: forall (m :: * -> *). Applicative m => Config m
dummyConfig =
Config
{ capacity :: Rational
capacity = Rational
0,
rate :: Rational
rate = Rational
0,
fillOnOverflow :: Bool
fillOnOverflow = Bool
True,
onEmpty :: m ()
onEmpty = () -> m ()
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
}
data State m = State
{ forall (m :: * -> *). State m -> Rational
level :: !Rational,
forall (m :: * -> *). State m -> Time
time :: !Time,
forall (m :: * -> *). State m -> Bool
paused :: !Bool,
forall (m :: * -> *). State m -> Int
configGeneration :: !Int,
forall (m :: * -> *). State m -> Config m
config :: !(Config m)
}
deriving ((forall x. State m -> Rep (State m) x)
-> (forall x. Rep (State m) x -> State m) -> Generic (State m)
forall x. Rep (State m) x -> State m
forall x. State m -> Rep (State m) x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall (m :: * -> *) x. Rep (State m) x -> State m
forall (m :: * -> *) x. State m -> Rep (State m) x
$cfrom :: forall (m :: * -> *) x. State m -> Rep (State m) x
from :: forall x. State m -> Rep (State m) x
$cto :: forall (m :: * -> *) x. Rep (State m) x -> State m
to :: forall x. Rep (State m) x -> State m
Generic)
deriving instance (NoThunks (m ())) => NoThunks (State m)
type Bucket m = StrictTVar m (State m)
data FillResult = Overflew | DidNotOverflow
data Handlers m = Handlers
{
forall (m :: * -> *).
Handlers m -> Rational -> Time -> STM m FillResult
fill ::
!( Rational ->
Time ->
STM m FillResult
),
forall (m :: * -> *). Handlers m -> Bool -> Time -> STM m ()
setPaused ::
!( Bool ->
Time ->
STM m ()
),
forall (m :: * -> *).
Handlers m
-> ((Rational, Config m) -> (Rational, Config m))
-> Time
-> STM m ()
updateConfig ::
!( ((Rational, Config m) -> (Rational, Config m)) ->
Time ->
STM m ()
)
}
fill' ::
( MonadMonotonicTime m,
MonadSTM m
) =>
Handlers m ->
Rational ->
m FillResult
fill' :: forall (m :: * -> *).
(MonadMonotonicTime m, MonadSTM m) =>
Handlers m -> Rational -> m FillResult
fill' Handlers m
h Rational
r = (Time -> STM m FillResult) -> m FillResult
forall (m :: * -> *) b.
(MonadMonotonicTime m, MonadSTM m) =>
(Time -> STM m b) -> m b
atomicallyWithMonotonicTime ((Time -> STM m FillResult) -> m FillResult)
-> (Time -> STM m FillResult) -> m FillResult
forall a b. (a -> b) -> a -> b
$ Handlers m -> Rational -> Time -> STM m FillResult
forall (m :: * -> *).
Handlers m -> Rational -> Time -> STM m FillResult
fill Handlers m
h Rational
r
setPaused' ::
( MonadMonotonicTime m,
MonadSTM m
) =>
Handlers m ->
Bool ->
m ()
setPaused' :: forall (m :: * -> *).
(MonadMonotonicTime m, MonadSTM m) =>
Handlers m -> Bool -> m ()
setPaused' Handlers m
h Bool
p = (Time -> STM m ()) -> m ()
forall (m :: * -> *) b.
(MonadMonotonicTime m, MonadSTM m) =>
(Time -> STM m b) -> m b
atomicallyWithMonotonicTime ((Time -> STM m ()) -> m ()) -> (Time -> STM m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ Handlers m -> Bool -> Time -> STM m ()
forall (m :: * -> *). Handlers m -> Bool -> Time -> STM m ()
setPaused Handlers m
h Bool
p
updateConfig' ::
( MonadMonotonicTime m,
MonadSTM m
) =>
Handlers m ->
((Rational, Config m) -> (Rational, Config m)) ->
m ()
updateConfig' :: forall (m :: * -> *).
(MonadMonotonicTime m, MonadSTM m) =>
Handlers m
-> ((Rational, Config m) -> (Rational, Config m)) -> m ()
updateConfig' Handlers m
h (Rational, Config m) -> (Rational, Config m)
f = (Time -> STM m ()) -> m ()
forall (m :: * -> *) b.
(MonadMonotonicTime m, MonadSTM m) =>
(Time -> STM m b) -> m b
atomicallyWithMonotonicTime ((Time -> STM m ()) -> m ()) -> (Time -> STM m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ Handlers m
-> ((Rational, Config m) -> (Rational, Config m))
-> Time
-> STM m ()
forall (m :: * -> *).
Handlers m
-> ((Rational, Config m) -> (Rational, Config m))
-> Time
-> STM m ()
updateConfig Handlers m
h (Rational, Config m) -> (Rational, Config m)
f
execAgainstBucket ::
( MonadDelay m,
MonadAsync m,
MonadFork m,
MonadMask m,
MonadTimer m,
NoThunks (m ())
) =>
Config m ->
(Handlers m -> m a) ->
m a
execAgainstBucket :: forall (m :: * -> *) a.
(MonadDelay m, MonadAsync m, MonadFork m, MonadMask m,
MonadTimer m, NoThunks (m ())) =>
Config m -> (Handlers m -> m a) -> m a
execAgainstBucket Config m
config Handlers m -> m a
action = (State m, a) -> a
forall a b. (a, b) -> b
snd ((State m, a) -> a) -> m (State m, a) -> m a
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Config m -> (Handlers m -> m a) -> m (State m, a)
forall (m :: * -> *) a.
(MonadDelay m, MonadAsync m, MonadFork m, MonadMask m,
MonadTimer m, NoThunks (m ())) =>
Config m -> (Handlers m -> m a) -> m (State m, a)
runAgainstBucket Config m
config Handlers m -> m a
action
execAgainstBucket' ::
( MonadDelay m,
MonadAsync m,
MonadFork m,
MonadMask m,
MonadTimer m,
NoThunks (m ())
) =>
(Handlers m -> m a) ->
m a
execAgainstBucket' :: forall (m :: * -> *) a.
(MonadDelay m, MonadAsync m, MonadFork m, MonadMask m,
MonadTimer m, NoThunks (m ())) =>
(Handlers m -> m a) -> m a
execAgainstBucket' Handlers m -> m a
action =
Config m -> (Handlers m -> m a) -> m a
forall (m :: * -> *) a.
(MonadDelay m, MonadAsync m, MonadFork m, MonadMask m,
MonadTimer m, NoThunks (m ())) =>
Config m -> (Handlers m -> m a) -> m a
execAgainstBucket Config m
forall (m :: * -> *). Applicative m => Config m
dummyConfig Handlers m -> m a
action
evalAgainstBucket ::
(MonadDelay m, MonadAsync m, MonadFork m, MonadMask m, MonadTimer m, NoThunks (m ())
) =>
Config m ->
(Handlers m -> m a) ->
m (State m)
evalAgainstBucket :: forall (m :: * -> *) a.
(MonadDelay m, MonadAsync m, MonadFork m, MonadMask m,
MonadTimer m, NoThunks (m ())) =>
Config m -> (Handlers m -> m a) -> m (State m)
evalAgainstBucket Config m
config Handlers m -> m a
action = (State m, a) -> State m
forall a b. (a, b) -> a
fst ((State m, a) -> State m) -> m (State m, a) -> m (State m)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Config m -> (Handlers m -> m a) -> m (State m, a)
forall (m :: * -> *) a.
(MonadDelay m, MonadAsync m, MonadFork m, MonadMask m,
MonadTimer m, NoThunks (m ())) =>
Config m -> (Handlers m -> m a) -> m (State m, a)
runAgainstBucket Config m
config Handlers m -> m a
action
runAgainstBucket ::
forall m a.
( MonadDelay m,
MonadAsync m,
MonadFork m,
MonadMask m,
MonadTimer m,
NoThunks (m ())
) =>
Config m ->
(Handlers m -> m a) ->
m (State m, a)
runAgainstBucket :: forall (m :: * -> *) a.
(MonadDelay m, MonadAsync m, MonadFork m, MonadMask m,
MonadTimer m, NoThunks (m ())) =>
Config m -> (Handlers m -> m a) -> m (State m, a)
runAgainstBucket Config m
config Handlers m -> m a
action = do
leakingPeriodVersionTMVar <- STM m (StrictTMVar m Int) -> m (StrictTMVar m Int)
forall a. HasCallStack => STM m a -> m a
forall (m :: * -> *) a.
(MonadSTM m, HasCallStack) =>
STM m a -> m a
atomically STM m (StrictTMVar m Int)
forall (m :: * -> *) a. MonadSTM m => STM m (StrictTMVar m a)
newEmptyTMVar
tid <- myThreadId
bucket <- init config
withAsync (do
labelThisThread "Leaky bucket (ouroboros-consensus)"
leak (readTMVar leakingPeriodVersionTMVar) tid bucket) $ \Async m ()
_ -> do
(Time -> STM m ()) -> m ()
forall (m :: * -> *) b.
(MonadMonotonicTime m, MonadSTM m) =>
(Time -> STM m b) -> m b
atomicallyWithMonotonicTime ((Time -> STM m ()) -> m ()) -> (Time -> STM m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ Maybe Int -> StrictTMVar m Int -> Bucket m -> Time -> STM m ()
maybeStartThread Maybe Int
forall a. Maybe a
Nothing StrictTMVar m Int
leakingPeriodVersionTMVar Bucket m
bucket
result <-
Handlers m -> m a
action (Handlers m -> m a) -> Handlers m -> m a
forall a b. (a -> b) -> a -> b
$
Handlers
{ fill :: Rational -> Time -> STM m FillResult
fill = \Rational
r Time
t -> ((State m, FillResult) -> FillResult
forall a b. (a, b) -> b
snd ((State m, FillResult) -> FillResult)
-> STM m (State m, FillResult) -> STM m FillResult
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>) (STM m (State m, FillResult) -> STM m FillResult)
-> STM m (State m, FillResult) -> STM m FillResult
forall a b. (a -> b) -> a -> b
$ Bucket m -> Rational -> Time -> STM m (State m, FillResult)
forall (m :: * -> *).
MonadSTM m =>
Bucket m -> Rational -> Time -> STM m (State m, FillResult)
snapshotFill Bucket m
bucket Rational
r Time
t,
setPaused :: Bool -> Time -> STM m ()
setPaused = Bucket m -> Bool -> Time -> STM m ()
setPaused Bucket m
bucket,
updateConfig :: ((Rational, Config m) -> (Rational, Config m)) -> Time -> STM m ()
updateConfig = StrictTMVar m Int
-> Bucket m
-> ((Rational, Config m) -> (Rational, Config m))
-> Time
-> STM m ()
updateConfig StrictTMVar m Int
leakingPeriodVersionTMVar Bucket m
bucket
}
state <- atomicallyWithMonotonicTime $ snapshot bucket
pure (state, result)
where
maybeStartThread :: Maybe Int -> StrictTMVar m Int -> Bucket m -> Time -> STM m ()
maybeStartThread :: Maybe Int -> StrictTMVar m Int -> Bucket m -> Time -> STM m ()
maybeStartThread Maybe Int
mLeakingPeriodVersion StrictTMVar m Int
leakingPeriodVersionTMVar Bucket m
bucket Time
time = do
State {config = Config {rate}} <- Bucket m -> Time -> STM m (State m)
forall (m :: * -> *).
MonadSTM m =>
Bucket m -> Time -> STM m (State m)
snapshot Bucket m
bucket Time
time
when (rate > 0) $ void $ tryPutTMVar leakingPeriodVersionTMVar $ maybe 0 (+ 1) mLeakingPeriodVersion
setPaused :: Bucket m -> Bool -> Time -> STM m ()
setPaused :: Bucket m -> Bool -> Time -> STM m ()
setPaused Bucket m
bucket Bool
paused Time
time = do
newState <- Bucket m -> Time -> STM m (State m)
forall (m :: * -> *).
MonadSTM m =>
Bucket m -> Time -> STM m (State m)
snapshot Bucket m
bucket Time
time
writeTVar bucket newState {paused}
updateConfig ::
StrictTMVar m Int ->
Bucket m ->
((Rational, Config m) -> (Rational, Config m)) ->
Time ->
STM m ()
updateConfig :: StrictTMVar m Int
-> Bucket m
-> ((Rational, Config m) -> (Rational, Config m))
-> Time
-> STM m ()
updateConfig StrictTMVar m Int
leakingPeriodVersionTMVar Bucket m
bucket (Rational, Config m) -> (Rational, Config m)
f Time
time = do
State
{ level = oldLevel,
paused,
configGeneration = oldConfigGeneration,
config = oldConfig
} <-
Bucket m -> Time -> STM m (State m)
forall (m :: * -> *).
MonadSTM m =>
Bucket m -> Time -> STM m (State m)
snapshot Bucket m
bucket Time
time
let (newLevel, newConfig) = f (oldLevel, oldConfig)
Config {capacity = newCapacity} = newConfig
newLevel' = (Rational, Rational) -> Rational -> Rational
forall a. Ord a => (a, a) -> a -> a
clamp (Rational
0, Rational
newCapacity) Rational
newLevel
writeTVar bucket $
State
{ level = newLevel',
time,
paused,
configGeneration = oldConfigGeneration + 1,
config = newConfig
}
mLeakingPeriodVersion <- tryTakeTMVar leakingPeriodVersionTMVar
maybeStartThread mLeakingPeriodVersion leakingPeriodVersionTMVar bucket time
init ::
(MonadMonotonicTime m, MonadSTM m, NoThunks (m ())) =>
Config m ->
m (Bucket m)
init :: forall (m :: * -> *).
(MonadMonotonicTime m, MonadSTM m, NoThunks (m ())) =>
Config m -> m (Bucket m)
init config :: Config m
config@Config {Rational
capacity :: forall (m :: * -> *). Config m -> Rational
capacity :: Rational
capacity} = do
time <- m Time
forall (m :: * -> *). MonadMonotonicTime m => m Time
getMonotonicTime
newTVarIO $
State
{ time,
level = capacity,
paused = False,
configGeneration = 0,
config = config
}
leak ::
( MonadDelay m,
MonadCatch m,
MonadFork m,
MonadAsync m,
MonadTimer m
) =>
STM m Int ->
ThreadId m ->
Bucket m ->
m ()
leak :: forall (m :: * -> *).
(MonadDelay m, MonadCatch m, MonadFork m, MonadAsync m,
MonadTimer m) =>
STM m Int -> ThreadId m -> Bucket m -> m ()
leak STM m Int
leakingPeriodVersionSTM ThreadId m
actionThreadId Bucket m
bucket = m () -> m ()
forall (f :: * -> *) a b. Applicative f => f a -> f b
forever (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
leakingPeriodVersion <- STM m Int -> m Int
forall a. HasCallStack => STM m a -> m a
forall (m :: * -> *) a.
(MonadSTM m, HasCallStack) =>
STM m a -> m a
atomically STM m Int
leakingPeriodVersionSTM
State {level, configGeneration = oldConfigGeneration, config = Config {rate, onEmpty}} <-
atomicallyWithMonotonicTime $ snapshot bucket
let timeToWait = Rational -> DiffTime
secondsRationalToDiffTime (Rational
level Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/ Rational
rate)
timeToWaitMicroseconds = DiffTime -> Int
diffTimeToMicrosecondsAsInt DiffTime
timeToWait
if level <= 0 || timeToWaitMicroseconds <= 0
then do
handle (\(SomeException
e :: SomeException) -> ThreadId m -> SomeException -> m ()
forall e. Exception e => ThreadId m -> e -> m ()
forall (m :: * -> *) e.
(MonadFork m, Exception e) =>
ThreadId m -> e -> m ()
throwTo ThreadId m
actionThreadId SomeException
e) onEmpty
void $ atomically $ blockUntilChanged configGeneration oldConfigGeneration $ readTVar bucket
else
assert (timeToWaitMicroseconds > 0) $ do
varTimeout <- registerDelay timeToWaitMicroseconds
atomically $
(check =<< TVar.readTVar varTimeout)
`orElse`
(void $ blockUntilChanged id leakingPeriodVersion leakingPeriodVersionSTM)
snapshot ::
( MonadSTM m
) =>
Bucket m ->
Time ->
STM m (State m)
snapshot :: forall (m :: * -> *).
MonadSTM m =>
Bucket m -> Time -> STM m (State m)
snapshot Bucket m
bucket Time
newTime = (State m, FillResult) -> State m
forall a b. (a, b) -> a
fst ((State m, FillResult) -> State m)
-> STM m (State m, FillResult) -> STM m (State m)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Bucket m -> Rational -> Time -> STM m (State m, FillResult)
forall (m :: * -> *).
MonadSTM m =>
Bucket m -> Rational -> Time -> STM m (State m, FillResult)
snapshotFill Bucket m
bucket Rational
0 Time
newTime
snapshotFill ::
( MonadSTM m
) =>
Bucket m ->
Rational ->
Time ->
STM m (State m, FillResult)
snapshotFill :: forall (m :: * -> *).
MonadSTM m =>
Bucket m -> Rational -> Time -> STM m (State m, FillResult)
snapshotFill Bucket m
bucket Rational
toAdd Time
newTime = do
State {level, time, paused, configGeneration, config = config} <- Bucket m -> STM m (State m)
forall (m :: * -> *) a. MonadSTM m => StrictTVar m a -> STM m a
readTVar Bucket m
bucket
let Config {rate, capacity, fillOnOverflow} = config
elapsed = Time -> Time -> DiffTime
diffTime Time
newTime Time
time
leaked = if Bool
paused then Rational
0 else (DiffTime -> Rational
diffTimeToSecondsRational DiffTime
elapsed Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Rational
rate)
levelLeaked = (Rational, Rational) -> Rational -> Rational
forall a. Ord a => (a, a) -> a -> a
clamp (Rational
0, Rational
capacity) (Rational
level Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
- Rational
leaked)
levelFilled = (Rational, Rational) -> Rational -> Rational
forall a. Ord a => (a, a) -> a -> a
clamp (Rational
0, Rational
capacity) (Rational
levelLeaked Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
+ Rational
toAdd)
overflew = Rational
levelLeaked Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
+ Rational
toAdd Rational -> Rational -> Bool
forall a. Ord a => a -> a -> Bool
> Rational
capacity
newLevel = if Bool -> Bool
not Bool
overflew Bool -> Bool -> Bool
|| Bool
fillOnOverflow then Rational
levelFilled else Rational
levelLeaked
!newState = State {time :: Time
time = Time
newTime, level :: Rational
level = Rational
newLevel, Bool
paused :: Bool
paused :: Bool
paused, Int
configGeneration :: Int
configGeneration :: Int
configGeneration, Config m
config :: Config m
config :: Config m
config}
writeTVar bucket newState
pure (newState, if overflew then Overflew else DidNotOverflow)
diffTimeToSecondsRational :: DiffTime -> Rational
diffTimeToSecondsRational :: DiffTime -> Rational
diffTimeToSecondsRational = (Integer -> Integer -> Rational
forall a. Integral a => a -> a -> Ratio a
% Integer
picosecondsPerSecond) (Integer -> Rational)
-> (DiffTime -> Integer) -> DiffTime -> Rational
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DiffTime -> Integer
diffTimeToPicoseconds
secondsRationalToDiffTime :: Rational -> DiffTime
secondsRationalToDiffTime :: Rational -> DiffTime
secondsRationalToDiffTime = Rational -> DiffTime
forall a b. (Real a, Fractional b) => a -> b
realToFrac
atomicallyWithMonotonicTime ::
( MonadMonotonicTime m,
MonadSTM m
) =>
(Time -> STM m b) ->
m b
atomicallyWithMonotonicTime :: forall (m :: * -> *) b.
(MonadMonotonicTime m, MonadSTM m) =>
(Time -> STM m b) -> m b
atomicallyWithMonotonicTime Time -> STM m b
f =
STM m b -> m b
forall a. HasCallStack => STM m a -> m a
forall (m :: * -> *) a.
(MonadSTM m, HasCallStack) =>
STM m a -> m a
atomically (STM m b -> m b) -> (Time -> STM m b) -> Time -> m b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Time -> STM m b
f (Time -> m b) -> m Time -> m b
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< m Time
forall (m :: * -> *). MonadMonotonicTime m => m Time
getMonotonicTime
clamp :: Ord a => (a, a) -> a -> a
clamp :: forall a. Ord a => (a, a) -> a -> a
clamp (a
low, a
high) a
x = a -> a -> a
forall a. Ord a => a -> a -> a
min a
high (a -> a -> a
forall a. Ord a => a -> a -> a
max a
low a
x)
microsecondsPerSecond :: Integer
microsecondsPerSecond :: Integer
microsecondsPerSecond = Integer
1_000_000
picosecondsPerSecond :: Integer
picosecondsPerSecond :: Integer
picosecondsPerSecond = Integer
1_000_000_000_000