module MOO.Builtins.Misc ( builtins ) where
import Control.Applicative ((<$>))
import Control.Monad.State (gets)
import Data.Monoid ((<>))
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, posixSecondsToUTCTime)
import Database.VCache (VCacheStats(..), vcacheStats)
# ifdef __GLASGOW_HASKELL__
import GHC.Stats (GCStats(currentBytesUsed, maxBytesUsed),
getGCStats, getGCStatsEnabled)
# endif
import MOO.Builtins.Common
import MOO.Database
import MOO.Object
import MOO.Task
import MOO.Types
import MOO.Util
import MOO.Version
import qualified MOO.String as Str
builtins :: [Builtin]
builtins = [
bf_pass
, bf_time
, bf_ctime
, bf_dump_database
, bf_shutdown
, bf_load_server_options
, bf_server_log
, bf_renumber
, bf_reset_max_object
, bf_server_version
, bf_memory_usage
, bf_db_disk_size
, bf_verb_cache_stats
, bf_log_cache_stats
]
bf_pass = Builtin "pass" 0 Nothing [] TAny $ \args -> do
(name, verbLoc, this) <- frame $ \frame ->
(verbName frame, verbLocation frame, initialThis frame)
maybeObject <- getObject verbLoc
case maybeObject >>= objectParent of
Just parent -> callVerb this parent name args
Nothing -> raise E_VERBNF
currentTime :: MOO IntT
currentTime = floor . utcTimeToPOSIXSeconds <$> gets startTime
bf_time = Builtin "time" 0 (Just 0) [] TInt $ \[] -> Int <$> currentTime
bf_ctime = Builtin "ctime" 0 (Just 1) [TInt] TStr $ \arg -> case arg of
[] -> ctime' =<< currentTime
[Int time] -> ctime' time
where ctime' :: IntT -> MOO Value
ctime' time = do
let utcTime = posixSecondsToUTCTime (fromIntegral time)
Str . Str.fromString <$>
ctime utcTime `catchUnsafeIOtoMOO` \_ -> raise E_INVARG
bf_dump_database = Builtin "dump_database" 0 (Just 0) [] TAny $ \[] ->
checkWizard >> getWorld >>= liftSTM . checkpoint >> return zero
bf_shutdown = Builtin "shutdown" 0 (Just 1) [TStr] TAny $ \optional -> do
let (message : _) = maybeDefaults optional
checkWizard
name <- getObjectName =<< frame permissions
let msg = "shutdown() called by " <> name
shutdown $ maybe msg (\(Str reason) -> msg <> ": " <> reason) message
return zero
bf_load_server_options = Builtin "load_server_options" 0 (Just 0)
[] TAny $ \[] ->
checkWizard >> loadServerOptions >> return zero
bf_server_log = Builtin "server_log" 1 (Just 2)
[TStr, TAny] TAny $ \(Str message : optional) -> do
let [is_error] = booleanDefaults optional [False]
errorMarker = if is_error then "*** " else ""
logMessage = errorMarker <> "> " <> Str.toText message
checkWizard
world <- getWorld
liftSTM $ writeLog world logMessage
return zero
bf_renumber = Builtin "renumber" 1 (Just 1) [TObj] TObj $ \[Obj object] -> do
checkValid object
checkWizard
(new, db) <- liftVTx . renumber object =<< getDatabase
putDatabase db
return (Obj new)
bf_reset_max_object = Builtin "reset_max_object" 0 (Just 0) [] TAny $ \[] -> do
checkWizard
getDatabase >>= liftVTx . resetMaxObject >>= putDatabase
return zero
bf_server_version = Builtin "server_version" 0 (Just 0) [] TStr $ \[] ->
return (Str $ Str.fromText serverVersion)
bf_memory_usage = Builtin "memory_usage" 0 (Just 0) [] TLst $ \[] ->
# ifdef __GLASGOW_HASKELL__
do maybeStats <- requestIO $ do
enabled <- getGCStatsEnabled
if enabled then Just <$> getGCStats else return Nothing
return $ case maybeStats of
Just stats ->
let nused = currentBytesUsed stats
nfree = maxBytesUsed stats nused
maxBlockSize = 2 ^ (floor $ logBase (2 :: Double) $
fromIntegral $ max nused nfree :: Int)
in fromListBy (fromListBy $ Int . fromIntegral) $
blocks maxBlockSize nused nfree
Nothing -> emptyList
where blocks :: (Integral a) => a -> a -> a -> [[a]]
blocks _ 0 0 = []
blocks blockSize nused nfree =
let nusedBlocks = nused `div` blockSize
nfreeBlocks = nfree `div` blockSize
rest = blocks (blockSize `div` 2)
(nused nusedBlocks * blockSize)
(nfree nfreeBlocks * blockSize)
in case (nusedBlocks, nfreeBlocks) of
(0, 0) -> rest
_ -> [blockSize, nusedBlocks, nfreeBlocks] : rest
# else
return emptyList
# endif
bf_db_disk_size = Builtin "db_disk_size" 0 (Just 1)
[TAny] TAny $ \optional -> do
let [full] = booleanDefaults optional [False]
stats <- unsafeIOtoMOO . vcacheStats =<< getVSpace
return $ if full
then fromList $ map (keyValue stats) [
("file_size", vcstat_file_size)
, ("vref_count", vcstat_vref_count)
, ("pvar_count", vcstat_pvar_count)
, ("root_count", vcstat_root_count)
, ("mem_vrefs", vcstat_mem_vrefs)
, ("mem_pvars", vcstat_mem_pvars)
, ("eph_count", vcstat_eph_count)
, ("alloc_count", vcstat_alloc_count)
, ("cache_limit", vcstat_cache_limit)
, ("cache_size", vcstat_cache_size)
, ("gc_count", vcstat_gc_count)
, ("write_pvars", vcstat_write_pvars)
, ("write_sync", vcstat_write_sync)
, ("write_frames", vcstat_write_frames)
]
else Int $ fromIntegral (vcstat_file_size stats)
where keyValue :: VCacheStats -> (StrT, VCacheStats -> Int) -> Value
keyValue stats (key, f) =
fromList [Str key, Int . fromIntegral $ f stats]
bf_verb_cache_stats = Builtin "verb_cache_stats" 0 (Just 0) [] TLst $ \[] ->
notyet "verb_cache_stats"
bf_log_cache_stats = Builtin "log_cache_stats" 0 (Just 0) [] TAny $ \[] ->
notyet "log_cache_stats"