{-# LANGUAGE ScopedTypeVariables, TypeFamilies, ParallelListComp, CPP, BangPatterns, TemplateHaskell #-} module Main where import Paths_timeplot (version) import Data.Version (showVersion) import Distribution.VcsRevision.Git import Language.Haskell.TH.Syntax import Control.Lens import Control.Monad import Data.Default import Data.List import Data.Ord import qualified Data.Map as M import qualified Data.ByteString.Char8 as S import System.Environment import System.Exit import Data.Time hiding (parseTime) import Graphics.Rendering.Chart import Graphics.Rendering.Chart.Backend.Cairo import Tools.TimePlot.Types import Tools.TimePlot.Conf import Tools.TimePlot.Source import Tools.TimePlot.Plots import Tools.TimePlot.Render import qualified Tools.TimePlot.Incremental as I -- Assume events are sorted. -- Pass 1: -- * Compute min/max times -- * Compute unique track names -- -- Then map track names to plotters (track types). -- -- Pass 2: -- * Generate plot data (one-pass multiplexed to tracks) -- makeChart :: (S.ByteString -> [(ChartKind LocalTime, S.ByteString)]) -> IO [(LocalTime, InEvent)] -> Maybe LocalTime -> Maybe LocalTime -> (LocalTime -> String -> String) -> IO (Renderable ()) makeChart chartKindF readEvents minT maxT transformLabel = do events <- readEvents if null events then return emptyRenderable else do let dropLateEvents es = case maxT of Just t -> takeWhile (( es -- Pass 1: find out min/max time and final track names. let i2o t (KindNone, _) = [] i2o t (KindWithin mapName sk, suf) = [(S.append (mapName t) suf, sk)] i2o t (k, suf) = [(S.append t suf, k)] let i2oTracks t = concatMap (i2o t) (chartKindF t) let t0 = fst (head events) let (minTime, maxTime, outTracks) = foldl' (\(!mi,!ma,!ts) (t,e) -> (min t mi, max t ma, foldr (uncurry M.insert) ts (i2oTracks $ evt_track e))) (t0, t0, M.empty) (dropLateEvents events) let minOutTime = case minT of Just t -> t ; Nothing -> minTime let maxOutTime = case maxT of Just t -> t ; Nothing -> maxTime let transformLabels axis = axis & axis_labels %~ map (map (\(t, s) -> (t, transformLabel t s))) let commonTimeAxis = transformLabels $ autoAxis [minOutTime, maxOutTime] -- Pass 2 events' <- dropLateEvents `fmap` readEvents let eventsToTracks = [(outTrack, (t,e)) | (t,e) <- events', (outTrack,_) <- i2oTracks (evt_track e)] let initPlot track = initGen (outTracks M.! track) (S.unpack track) minTime maxTime let plots = I.runStreamSummary (I.byKey initPlot) eventsToTracks -- Render return $ renderStackedLayouts $ slayouts_layouts .~ map (dataToPlot commonTimeAxis (minOutTime,maxOutTime)) (M.elems plots) $ def showHelp = mapM_ putStrLn [ "", "tplot - a tool for drawing timing diagrams.", " See http://www.haskell.org/haskellwiki/Timeplot", "Usage: tplot [-o OFILE] [-of {png|pdf|ps|svg}] [-or 640x480]", " -if IFILE [-tf TF] ", " [{+|-}k Pat1 '[+Suf1] Kind1' {+|-}k Pat2 '[+Suf2] Kind2' ...] [{+|-}dk '[+Suf] KindN']", " [-fromTime TIME] [-toTime TIME] [-baseTime TIME]", " --version - show version information", " --help - show this help", " -o OFILE - output file", " -of - output format (default: extension of -o)", " -or - output resolution (default 640x480)", " -if IFILE - input file; '-' means 'read from stdin'", " NOTE: for large datasets, use actual files, not stdin,", " as tplot can operate on them in streaming mode, which", " allows it to use a lot less memory and work on virtually", " unbounded datasets", " -tf TF - time format: -tf 'date PATTERN' means that times are dates in the format", " specified by PATTERN - see http://linux.die.net/man/3/strptime, ", " for example, -tf 'date [%Y-%m-%d %H:%M:%S]' parses dates like ", " '[2009-10-20 16:52:43]'.", " We also support %OS for fractional seconds (i.e. %OS will", " parse 12.4039 or 12,4039) and %^[+-][N]s for ten-powers ", " of seconds since epoch, for example %^-3s is ms since epoch.", " -tf elapsed means times are fractional seconds since an unknown moment.", " Default: 'date %Y-%m-%d %H:%M:%OS'", " {+|-}dk - set default diagram kind", " {+|-}k P K - set diagram kind for tracks matching regex P (in the format", " of regex-tdfa, which is at least POSIX-compliant and", " supports some GNU extensions) to K", " EXPLANATION:", " -k clauses are matched till first success, +k are all", " matched: a track is drawn acc. to all matching +k, to +dk", " AND ALSO to the first matching -k, or -dk if none of -k", " match", " EXPLANATION OF SUF:", " If '+Suf' is present (e.g. +k request '+frequency freq 60'),", " then '.Suf' is appended to the input track name while mapping it", " to the output track. This is so that a single input track can", " participate in many output tracks - tplot can have only one output", " track with a particular name, so you can't have one track named", " 'request' which draws 'freq 60' and another one for 'hist 60' - ", " you should use +k request '+frequency freq 60' +k request '+histogram hist 60'", " and you'll get output tracks 'request.frequency' and 'request.histogram'", " -fromTime - filter records whose time is >= this time", " (formatted according to -tf)", " -toTime - filter records whose time is < this time", " (formatted according to -tf)", " -baseTime - display time difference with this value instead of absolute time", " (formatted according to -tf)", "", "Input format: lines of the following form:", "1234 >A - at time 1234, activity A has begun", "1234 ... <), for example 'duration quantile", " 300 0.25,0.5,0.75' will plot these quantiles of durations of the", " events. This is useful where your log looks like 'Started processing'", " ... 'Finished processing': you can plot processing durations without", " computing them yourself. Very useful inside 'within'!", " If you use 'drop', then names of the original input tracks will be dropped", " before putting the events onto the output track, e.g. an event rtime.14e3ac1", " when used by 'within[.] duration drop dots', will be put onto the output track", " 'rtime', with input track 'rtime'. When used by 'within[.] duration dots',", " its input track will still be rtime.14e3ac1. The difference is whether", " the output of 'duration' appears to 'XXXX' as a single or multiple input tracks.", " E.g. if you're measuring durations of processing unique requests with rtime.REQID,", " then use 'drop'; if it's durations of processing at certain stages with rtime.STAGE", " then don't.", " 'within[C] XXXX' - draw plot XXXX over events grouped by their track's name ", " before separator C. For example, if you have processes", " named 'MACHINE-PID' (i.e. UNIT027-8532) say 'begin something' / ", " 'end something' and you're interested in the properties of per-machine", " durations, use within[-] duration dots; or if you've got jobs starting", " and finishing tasks on different machines, and you want to plot a diagram", " showing the number of utilized machines and how this number is composed of", " utilization by different jobs, make your trace say '>job-JOBID'...' customer, John), ", " group the events by supertrack and for each supertrack draw a graphical track", " using the plot type SOMETHING. It's up to SOMETHING to do something with these", " events, e.g. 'lines' will simply draw several line plots, one per subtrack.", " 'acount N' is for activity counts: a histogram is drawn with granularity", " of N time units, where the bin corresponding to [t..t+N) has value", " 'what was the average number of active events or impulses in that", " interval'. When used inside 'within', the histogram is a stacked one,", " with one vertical bar per subtrack in each bin.", " 'count N' is same as 'acount N' but scaled by the bin size, i.e. rather than ", " activity rate per time unit, it's activity count per time bin. ", " 'apercent N B' is for activity percentages of a basis: like 'acount N',", " but instead of X you get 100*X/B", " 'afreq N' is for activity frequencies: it's like acount, but relative", " rather than absolute - it only makes sense inside 'within', because", " otherwise it would just always show a filled one-coloured bar in every bin.", " 'freq N [TYPE]' is for event frequency histograms: a histogram of type", " TYPE (stacked or clustered, default clustered) is drawn for each time", " bin of size N, about the *frequency* of various ` events", " 'hist N [TYPE]' is for event count histograms: a histogram of type TYPE", " (stacked or clustered, default clustered) is drawn for each time bin", " of size N, about the *counts* of various ` events", " 'quantile N q1,q2,..' (example: quantile 100 0.25,0.5,0.75) - a bar chart", " of corresponding quantiles in time bins of size N", " 'binf N v1,v2,..' (example: binf 100 1,2,5,10) - a histogram of frequency", " of values falling into bins min..v1, v1..v2, .., v2..max in time bins", " of size N", " 'binh N v1,v2,..' (example: binf 100 1,2,5,10) - a histogram of counts of", " values falling into bins min..v1, v1..v2, .., v2..max in time bins of", " size N", " 'lines' - a simple line plot of numeric values. When used in 'within', ", " gives one plot per subtrack.", " 'dots' - a simple dot plot of numeric values. When used in 'within', ", " gives one plot per subtrack.", " 'dots ALPHA' - a simple dot plot of numeric values. When used in 'within', ", " gives one plot per subtrack. All dots are drawn with opacity ALPHA,", " where 0 means transparent and 1 means opaque. Useful when you're suffering", " from overplotting (dots overlapping each other too much)", " 'cumsum N [TYPE]' - a simple line plot of the running sum of the numeric values,", " over bins of size N - as if you plotted 'sum N', but sums are accumulated over time", " When used in 'within', produce 1 subplot per subtrack. TYPE can be: ", " 'overlayed' -> just lay the subplots over one another.", " 'stacked' -> add them up at each point to see how subtracks contribute", " to the total cumulative sum (default; only makes sense inside 'within')", " 'sum N [TYPE]' - a simple line plot of the sum of the numeric values in time", " bins of size N. N is measured in units or in seconds.", " When used in 'within', produce 1 subplot per subtrack. TYPE used in same ", " way as in cumsum." ] showGitVersion = $(do v <- qRunIO getRevision lift $ case v of Nothing -> "" Just (hash,True) -> hash ++ " (with local modifications)" Just (hash,False) -> hash) main = do args <- getArgs mainWithArgs args mainWithArgs args = do when (null args || args == ["--help"]) $ showHelp >> exitSuccess when (null args || args == ["--version"]) $ do putStrLn ("This is timeplot-" ++ showVersion version ++ " (git " ++ showGitVersion ++ ")") >> exitSuccess let !conf = readConf args let format = case outFormat conf of { OutPNG -> PNG ; OutPDF -> PDF ; OutPS -> PS ; OutSVG -> SVG } let render r w h f = renderableToFile (fo_size .~ (w, h) $ fo_format .~ format $ def) f r case conf of ConcreteConf { parseTime=parseTime, inFile=inFile, chartKindF=chartKindF, outFile=outFile, outResolution=outResolution, fromTime=fromTime, toTime=toTime, transformLabel=transformLabel } -> do source <- case inFile of "-" -> do putStrLn "Warning: working in non-streaming mode (for very large datasets, supply input from a file)" events <- readSource parseTime inFile return (return events) _ -> return (readSource parseTime inFile) chart <- makeChart chartKindF source fromTime toTime transformLabel let (w,h) = outResolution render chart w h outFile