packages feed

greg-client (empty) → 1.0.0

raw patch · 5 files changed

+403/−0 lines, 5 filesdep +basedep +binarydep +bytestringsetup-changed

Dependencies added: base, binary, bytestring, clock, hostname, network, stm, system-uuid, time

Files

+ LICENSE view
@@ -0,0 +1,31 @@+Copyright (c) 2009, Eugene Kirpichov++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * The names of contributors may not be used to endorse or promote+      products derived from this software without specific prior+      written permission. ++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,8 @@+#!/usr/bin/env runghc++module Main where++import Distribution.Simple++main :: IO ()+main = defaultMain
+ System/Log/Greg.hs view
@@ -0,0 +1,292 @@+{-# LANGUAGE CPP #-}+-------------------------------------------------------------------------------+-- |+-- Copyright        :   (c) 2010 Eugene Kirpichov, Dmitry Astapov+-- License          :   BSD3+-- +-- Maintainer       :   Eugene Kirpichov <ekirpichov@gmail.com>, +--                      Dmitry Astapov <dastapov@gmail.com>+-- Stability        :   experimental+-- Portability      :   GHC only (STM, GHC.Conc for unsafeIOToSTM)+-- +-- This module provides a binding to the greg distributed logger,+-- which provides a high-precision global time axis and is very performant.+-- +-- See project home page at <http://code.google.com/p/greg> for an explanation+-- of how to use the server, the features, motivation and design.+--++module System.Log.Greg (+    Configuration(..)+   ,logMessage+   ,withGregDo+   ,defaultConfiguration+   ) where++import System.Log.PreciseClock+import System.Posix.Clock++import Data.ByteString.Unsafe+import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString.Lazy.Char8 as L+import Data.Binary+import Data.Binary.Put++import Network+import Network.HostName (getHostName)++import System.UUID.V4++import System.IO+import Foreign++#ifdef DEBUG+import Debug.Trace+#endif++import qualified Control.Exception as E+import Control.Concurrent+import Control.Concurrent.STM+import GHC.Conc+import Control.Monad++{-+Messages are stored in TChan+1 thread performs calibration+1 'packer' thread takes messages from tchan and offloads them to sender thread(s).+1 'checking' thread keeps an eye on TChan size, initiates message dropping if necessary.+1 'sender' thread delivers the batch of messages to the server+-}++data Record = Record {+        timestamp :: TimeSpec,+        message :: B.ByteString+    }++data GregState = GregState { +        configuration :: Configuration,+        records :: TChan Record, -- FIFO for queued Records+        numRecords :: TVar Int, -- How many records are in FIFO+        isDropping :: TVar Bool, -- True is we are not adding records to the FIFO since there are more than 'maxBufferedRecords' of them+        packet :: TMVar [Record] -- Block of records we are currently trying to send+    }++-- | Client configuration.+-- You probably only need to change @server@.+data Configuration = Configuration {+        server                  :: String   -- ^ Server hostname (default @localhost@)+       ,port                    :: Int      -- ^ Message port (default @5676@)+       ,calibrationPort         :: Int      -- ^ Calibration port (default @5677@)+       ,flushPeriodMs           :: Int      -- ^ How often to send message batches to server +                                            --   (default @1000@)+       ,clientId                :: String   -- ^ Arbitrary identifier, will show up in logs.+                                            --   For example, @\"DataService\"@ +                                            --   (default @\"unknown\"@)+       ,maxBufferedRecords      :: Int      -- ^ How many records to store between flushes+                                            --   (more will be dropped) (default @100000@)+       ,useCompression          :: Bool     -- ^ Whether to use gzip compression +                                            --   (default @False@, @True@ is unsupported)+       ,calibrationPeriodSec    :: Int      -- ^ How often to initiate calibration exchanges+                                            --   (default @10@)+    }++hostname, ourUuid :: B.ByteString+hostname = B.pack $ unsafePerformIO getHostName+ourUuid = repack . runPut . put $ unsafePerformIO uuid++-- | The default configuration, suitable for most needs.+defaultConfiguration :: Configuration+defaultConfiguration = Configuration {+        server = "localhost",+        port = 5676,+        calibrationPort = 5677,+        flushPeriodMs = 1000,+        clientId = "unknown",+        maxBufferedRecords = 100000,+        useCompression = True,+        calibrationPeriodSec = 10+    }++-- | Perform an IO action with logging (will wait for all messages to flush).+withGregDo :: Configuration -> IO () -> IO ()+withGregDo conf realMain = withSocketsDo $ do+  st <- atomically $ do st <- readTVar state+                        let st' = st{configuration = conf}+                        writeTVar state $ st'+                        return st'+  +  let everyMs ms action = forkIO $ forever (action >> threadDelay (1000 * ms))+  let safely action label = action `E.catch` \e -> putStrLnT ("Error in " ++ label ++ ": " ++ show (e::E.SomeException))+  let safelyEveryMs ms action label = everyMs ms (safely action label)++  -- Packer thread offloads records to sender thread+  -- Housekeeping thread keeps queue size at check+  calTID   <- safelyEveryMs (1000*calibrationPeriodSec conf) (initiateCalibrationOnce st) "calibrator"+  packTID  <- safelyEveryMs (            flushPeriodMs conf) (packRecordsOnce         st) "packer"+  checkTID <- safelyEveryMs (            flushPeriodMs conf) (checkQueueSize          st) "queue size checker"+  sendTID  <- safelyEveryMs (            flushPeriodMs conf) (sendPacketOnce          st) "sender"++  realMain+  putStrLnT "Flushing remaining messages"+  +  -- Shutdown. For now, just wait untill all messages are out of the queue+  -- 1. Stop reception of new messages+  killThread checkTID+  atomically $ writeTVar (isDropping st) True+  -- 2. Wait until all messages are sent+  let waitFlush = do+        numrs <- atomically $ readTVar (numRecords st)+        unless (numrs == 0) $ threadDelay (1000*flushPeriodMs conf) >> waitFlush+  waitFlush+  killThread packTID+  atomically $ putTMVar (packet st) []+  let waitSend = do+        sent <- atomically $ isEmptyTMVar (packet st)+        unless sent $ threadDelay (1000*flushPeriodMs conf) >> waitSend+  waitSend+  killThread sendTID+  killThread calTID+  putStrLnT "Shutdown finished."++checkQueueSize :: GregState -> IO ()+checkQueueSize st = do+  currsize <- atomically $ readTVar (numRecords st)+  let maxrs = maxBufferedRecords (configuration st)+  droppingNow <- atomically $ readTVar (isDropping st)+  case (droppingNow, currsize > maxrs) of+    (True , True) -> putStrLnT ("Still dropping (queue " ++ show currsize ++ ")")+    (False, True) -> do putStrLnT ("Started to drop (queue " ++ show currsize ++ ")")+                        atomically $ writeTVar (isDropping st) True+    (True, False) -> do putStrLnT ("Stopped dropping (queue " ++ show currsize ++ ")")+                        atomically $ writeTVar (isDropping st) False+    (False, False) -> return () -- everything is OK++packRecordsOnce :: GregState -> IO ()+packRecordsOnce st = atomically $ do+  putStrLnT $ "Packing: reading all messages ..."+  rs <- readAtMost (10000::Int) -- Mandated by protocol+  putStrLnT $ "Packing: reading all messages done (" ++ show (length rs) ++ ")"+  unless (null rs) $ do+    putStrLnT $ "Packing " ++ show (length rs) ++ " records"+    atomModTVar (numRecords st) (\x -> x - length rs) -- decrease queue length+    senderAccepted <- tryPutTMVar (packet st) rs -- putting messages in the outbox+    unless senderAccepted retry +    putStrLnT "Packing done"+  where+    readAtMost 0 = return []+    readAtMost n = do +      empty <- isEmptyTChan (records st)+      if empty then return []+        else do r <- readTChan (records st)+                rest <- readAtMost (n-1)+                return (r:rest)++sendPacketOnce :: GregState -> IO ()+sendPacketOnce st = atomically $ withWarning "Failed to pack/send records" $ do+  rs <- takeTMVar $ packet st+  unless (null rs) $ do+    let conf = configuration st+    putStrLnT "Pushing records"+    unsafeIOToSTM $ E.bracket (connectTo (server conf) (PortNumber $ fromIntegral $ port conf)) hClose $ \hdl -> do+      putStrLnT "Pushing records - connected"+      let msg = formatRecords (configuration st) rs+      putStrLnT $ "Snapshotted " ++ show (length rs) ++ " records --> " ++ show (B.length msg) ++ " bytes"+      unsafeUseAsCStringLen msg $ \(ptr, len) -> hPutBuf hdl ptr len+      hFlush hdl+    putStrLnT $ "Pushing records - done"+  where+    withWarning s t = (t `catchSTM` (\e -> putStrLnT (s ++ ": " ++ show e) >> check False)) `orElse` return ()+   ++formatRecords :: Configuration -> [Record] -> B.ByteString+formatRecords conf records = repack . runPut $ do+  putByteString ourUuid+  putWord8 0+  putWord32le (fromIntegral $ length $ clientId conf)+  putByteString (B.pack $ clientId conf)+  mapM_ putRecord records+  putWord32le 0++putRecord :: Record -> Put+putRecord r = do+  putWord32le 1+  putWord64le (toNanos64 (timestamp r))+  putWord32le (fromIntegral $ B.length hostname)+  putByteString hostname+  putWord32le (fromIntegral $ B.length (message r))+  putByteString (message r)++initiateCalibrationOnce :: GregState -> IO ()+initiateCalibrationOnce st = do+  putStrLnT "Initiating calibration"+  let conf = configuration st+  E.bracket (connectTo (server conf) (PortNumber $ fromIntegral $ calibrationPort conf)) hClose $ \hdl -> do+    hSetBuffering hdl NoBuffering+    putStrLnT "Calibration - connected"+    unsafeUseAsCString ourUuid $ \p -> hPutBuf hdl p 16+    allocaBytes 8 $ \pTheirTimestamp -> do+      let whenM mp m = mp >>= \v -> when v m+          loop = whenM (hSkipBytes hdl 8 pTheirTimestamp) $ do+                   ts <- preciseTimeSpec+                   let pOurTimestamp = repack $ runPut $ putWord64le (toNanos64 ts)+                   unsafeUseAsCString pOurTimestamp $ \ptr -> hPutBuf hdl ptr 8+                   -- putStrLnT "Calibration - next loop iteration passed"+                   loop+      loop+  putStrLnT "Calibration ended - sleeping"++state :: TVar GregState+state = unsafePerformIO $ do rs <- newTChanIO+                             numrs <- newTVarIO 0+                             dropping <- newTVarIO False+                             pkt <- newEmptyTMVarIO+                             newTVarIO $ GregState defaultConfiguration rs numrs dropping pkt+-- | Log a message. The message will show up in server's output+-- annotated with a global timestamp (client's clock offset does +-- not matter).+logMessage :: String -> IO ()+logMessage s = do+  t <- preciseTimeSpec+  st <- atomically $ readTVar state+  shouldDrop <- atomically $ readTVar (isDropping st)+  unless shouldDrop $ atomically $ do+    writeTChan (records st) (Record {timestamp = t, message = B.pack s})+    atomModTVar (numRecords st) (+1)++--------------------------------------------------------------------------+-- Utilities++toNanos64 :: TimeSpec -> Word64+toNanos64 (TimeSpec s ns) = fromIntegral ns + 1000000000*fromIntegral s++hSkipBytes :: Handle -> Int -> Ptr a -> IO Bool+hSkipBytes _ 0 _ = return True+hSkipBytes h n p = do+  closed <- hIsEOF h+  if closed +    then return False+    else do skipped <- hGetBuf h p n +            if skipped < 0 +              then return False +              else hSkipBytes h (n-skipped) p++repack :: L.ByteString -> B.ByteString+repack = B.concat . L.toChunks++atomModTVar :: TVar a -> (a -> a) -> STM ()+atomModTVar var f = readTVar var >>= \val -> writeTVar var (f val)++putStrLnT :: (Monad m) => String -> m ()+#ifdef DEBUG+putStrLnT s = trace s $ return ()+#else+putStrLnT _ = return ()+#endif++#ifdef DEBUG+testFlood :: IO ()+testFlood = withGregDo defaultConfiguration $ forever $ logMessage "Hello" -- >> threadDelay 1000++testSequence :: IO ()+testSequence = withGregDo defaultConfiguration $ mapM_ (\x -> logMessage (show x) >> threadDelay 100000) [1..]+#endif
+ System/Log/PreciseClock.hs view
@@ -0,0 +1,39 @@+module System.Log.PreciseClock (preciseTimeSpec, preciseTimestamp) where
+
+import System.Posix.Clock
+import Data.Time.Clock
+
+import Control.Monad
+import System.IO.Unsafe
+
+seedRealtime :: TimeSpec
+seedMonotonic :: TimeSpec
+(seedRealtime, seedMonotonic) = unsafePerformIO $ liftM2 (,) (getTime Realtime) (getTime Monotonic)
+
+epochStart :: UTCTime
+epochStart = UTCTime (toEnum 40587) 0
+
+clock2utc :: TimeSpec -> UTCTime
+clock2utc spec = toDiffTime spec `addUTCTime` epochStart
+
+toDiffTime :: TimeSpec -> NominalDiffTime
+toDiffTime (TimeSpec sec nsec) = fromIntegral sec + fromIntegral nsec / 1000000000
+
+preciseTimeSpec :: IO TimeSpec
+preciseTimeSpec = do
+  TimeSpec ms mns <- getTime Monotonic
+  let (TimeSpec rts0 rtns0) = seedRealtime
+  let (TimeSpec ms0  mns0)  = seedMonotonic
+  return $ normalize $ TimeSpec (rts0 + ms - ms0) (rtns0 + mns - mns0)
+
+nanosInSec :: (Integral a) => a
+nanosInSec = 1000000000
+
+normalize :: TimeSpec -> TimeSpec
+normalize ts@(TimeSpec s ns) 
+  | ns < 0          = normalize (TimeSpec (s-1) (ns+nanosInSec))
+  | ns > nanosInSec = normalize (TimeSpec (s+1) (ns-nanosInSec))
+  | otherwise       = ts
+
+preciseTimestamp :: IO UTCTime
+preciseTimestamp = clock2utc `fmap` preciseTimeSpec
+ greg-client.cabal view
@@ -0,0 +1,33 @@+Name: greg-client+Version: 1.0.0+License: BSD3+License-file: LICENSE+Copyright: Eugene Kirpichov, Dmitry Astapov 2010+Author: Eugene Kirpichov <ekirpichov@gmail.com>, Dmitry Astapov <dastapov@gmail.com>+Maintainer: Eugene Kirpichov <ekirpichov@gmail.com>, Dmitry Astapov <dastapov@gmail.com>+Synopsis: A scalable distributed logger with a high-precision global time axis.+Description: Haskell client to the \"greg\" distributed logger with a high-precision global time axis (<http://code.google.com/p/greg/>).+Category: Logging+Cabal-Version: >= 1.6+Build-Type: Simple+Homepage: http://code.google.com/p/greg/+Source-repository head+  type: git+  location: git://github.com/jkff/greg.git++flag debug+  description: enable debugging output+  default: False++library+  Build-Depends: base >= 4 && < 5++  if flag(debug)+    cpp-options: -DDEBUG++  hs-source-dirs: .+  ghc-options: -Wall -fno-warn-name-shadowing -fno-warn-orphans+  Build-Depends: bytestring, time, binary,  +                 system-uuid, hostname, network < 2.3, clock >= 0.1.2.1, stm+  Exposed-Modules: System.Log.Greg+  Other-Modules: System.Log.PreciseClock