私は、apache ログ ファイルから何らかの興味深い (実際にはそうではない) 統計を読み込んで解析し、導出するための小さなスクリプトを用意しています。これまでのところ、ログ ファイル内のすべての要求で送信されたバイトの合計数と、最も一般的な IP アドレスの上位 10 という 2 つの簡単なオプションを作成しました。
最初の「モード」は、解析されたすべてのバイトの単純な合計です。2 つ目は、マップ (Data.Map) の折り畳みでinsertWith (+) 1'、出現回数をカウントするために使用します。
最初のものは期待どおりに実行され、ほとんどの時間は解析に費やされ、一定のスペースで実行されます。
42,359,709,344 バイトがヒープに割り当てられた 72,405,840 バイトが GC 中にコピーされた
世代 0: 76311 コレクション、
0 並列、0.89 秒、0.99 秒経過
世代 1: 1553 コレクション、0 並列、0.21 秒、0.22 秒経過INIT 時間 0.00 秒 ( 0.00 秒経過) MUT 時間 21.76 秒 ( 24.82 秒経過) GC 時間 1.10 秒 ( 1.20 秒経過) EXIT 時間
0.00 秒 ( 0.00 秒経過) 合計時間 22.87 秒 ( 26.02 秒経過)%GC 時間 4.8% (4.6% 経過)
割り当てレート 1,946,258,962 バイト/MUT 秒
生産性 総ユーザーの 95.2%、総経過時間の 83.6%
しかし、2番目のものはそうではありません!
ヒープに割り当てられた 49,398,834,152 バイト GC 中にコピーされた 580,579,208 バイト 最大常駐 (15 サンプル) 718,385,088 バイト 最大スロップ 134,532,128 バイト
ジェネレーション 0: 91275 コレクション、
0 並列、252.65 秒、254.46 秒経過
ジェネレーション 1: 15 コレクション、0 並列、0.12 秒、0.12 秒経過INIT 時間 0.00 秒 (0.00 秒経過) MUT 時間 41.11 秒 (48.87 秒経過) GC 時間 252.77 秒 (254.58 秒経過) EXIT 時間
0.00 秒 (0.01 秒経過) 合計時間 293.88 秒 (303.45 秒経過)%GC 時間 86.0% (83.9% 経過)
割り当て率 1,201,635,385 バイト/MUT 秒
生産性 総ユーザーの 14.0%、総経過時間の 13.5%
そして、これがコードです。
{-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Data.Attoparsec.Lazy as AL
import Data.Attoparsec.Char8 hiding (space, take)
import qualified Data.ByteString.Char8 as S
import qualified Data.ByteString.Lazy.Char8 as L
import Control.Monad (liftM)
import System.Environment (getArgs)
import Prelude hiding (takeWhile)
import qualified Data.Map as M
import Data.List (foldl', sortBy)
import Text.Printf (printf)
import Data.Maybe (fromMaybe)
type Command = String
data LogLine = LogLine {
getIP :: S.ByteString,
getIdent :: S.ByteString,
getUser :: S.ByteString,
getDate :: S.ByteString,
getReq :: S.ByteString,
getStatus :: S.ByteString,
getBytes :: S.ByteString,
getPath :: S.ByteString,
getUA :: S.ByteString
} deriving (Ord, Show, Eq)
quote, lbrack, rbrack, space :: Parser Char
quote = satisfy (== '\"')
lbrack = satisfy (== '[')
rbrack = satisfy (== ']')
space = satisfy (== ' ')
quotedVal :: Parser S.ByteString
quotedVal = do
quote
res <- takeTill (== '\"')
quote
return res
bracketedVal :: Parser S.ByteString
bracketedVal = do
lbrack
res <- takeTill (== ']')
rbrack
return res
val :: Parser S.ByteString
val = takeTill (== ' ')
line :: Parser LogLine
l ine = do
ip <- val
space
identity <- val
space
user <- val
space
date <- bracketedVal
space
req <- quotedVal
space
status <- val
space
bytes <- val
(path,ua) <- option ("","") combined
return $ LogLine ip identity user date req status bytes path ua
combined :: Parser (S.ByteString,S.ByteString)
combined = do
space
path <- quotedVal
space
ua <- quotedVal
return (path,ua)
countBytes :: [L.ByteString] -> Int
countBytes = foldl' count 0
where
count acc l = case AL.maybeResult $ AL.parse line l of
Just x -> (acc +) . maybe 0 fst . S.readInt . getBytes $ x
Nothing -> acc
countIPs :: [L.ByteString] -> M.Map S.ByteString Int
countIPs = foldl' count M.empty
where
count acc l = case AL.maybeResult $ AL.parse line l of
Just x -> M.insertWith' (+) (getIP x) 1 acc
Nothing -> acc
---------------------------------------------------------------------------------
main :: IO ()
main = do
[cmd,path] <- getArgs
dispatch cmd path
pretty :: Show a => Int -> (a, Int) -> String
pretty i (bs, n) = printf "%d: %s, %d" i (show bs) n
dispatch :: Command -> FilePath -> IO ()
dispatch cmd path = action path
where
action = fromMaybe err (lookup cmd actions)
err = printf "Error: %s is not a valid command." cmd
actions :: [(Command, FilePath -> IO ())]
actions = [("bytes", countTotalBytes)
,("ips", topListIP)]
countTotalBytes :: FilePath -> IO ()
countTotalBytes path = print . countBytes . L.lines =<< L.readFile path
topListIP :: FilePath -> IO ()
topListIP path = do
f <- liftM L.lines $ L.readFile path
let mostPopular (_,a) (_,b) = compare b a
m = countIPs f
mapM_ putStrLn . zipWith pretty [1..] . take 10 . sortBy mostPopular . M.toList $ m
編集:
+RTS -A16M を追加すると、GC が 20% に減少しました。もちろんメモリ使用量は変わりません。