-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathintercept2human.hs
executable file
·139 lines (127 loc) · 4.01 KB
/
intercept2human.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env cabal
{- cabal:
build-depends: aeson
, vector
, base
, time
, containers
, text
, bytestring
-}
{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
-- |
-- Script that processes @events.json@ from @intercept(1)@ (from the @bear@
-- package), and displays them nicely.
module Main where
import Data.Maybe (fromMaybe)
import Control.Monad (msum)
import Data.Aeson
import Data.Aeson.Types (parseEither)
import Data.ByteString.Lazy.Char8 qualified as BS
import GHC.Stack (HasCallStack)
import Data.Either (fromRight)
import Data.Fixed (showFixed)
import Data.Map.Strict qualified as Map
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Encoding qualified as T
import Data.Time
import Data.Time.Format.ISO8601 (iso8601ParseM)
import Data.Vector (Vector)
import Data.Vector qualified as V
import System.Environment (getArgs)
data Event
= Start !StartEvent
| Terminate !TerminateEvent
| StartAndTerminate !(StartEvent, TerminateEvent)
deriving stock (Show)
instance Semigroup Event where
Start s <> Terminate e = StartAndTerminate (s, e)
Terminate e <> Start s = StartAndTerminate (s, e)
l <> r = error $ "duplicate " <> show l <> show r
data StartEvent = StartEvent
{ executable :: Text
, arguments :: Vector Text
, startTime :: UTCTime
, pid :: Int
, ppid :: Int
}
deriving stock (Show)
data TerminateEvent = TerminateEvent
{status :: Text, endTime :: UTCTime}
deriving stock (Show)
procOne :: BS.ByteString -> Either String (Text, Event)
procOne d =
eitherDecode @Value d
>>= parseEither
( withObject "run" \o -> do
rid <- o .: "rid"
started <- o .:? "started"
terminated <- o .:? "terminated"
timestamp <- iso8601ParseM =<< o .: "timestamp"
r <-
fromMaybe (fail "unmatched variant") $
msum @[] @Maybe
[ parseTerminated timestamp <$> terminated
, parseStarted timestamp <$> started
]
pure (rid, r)
)
where
parseTerminated endTime = withObject "terminated" \o -> do
status <- o .: "status"
pure $ Terminate TerminateEvent {..}
parseStarted startTime = withObject "started" \o -> do
exe <- o .: "execution"
executable <- exe .: "executable"
arguments <- exe .: "arguments"
pid <- o .: "pid"
ppid <- o .: "ppid"
pure $ Start StartEvent {..}
throwLeft :: HasCallStack => Either String a -> a
throwLeft (Left a) = error a
throwLeft (Right v) = v
proc :: BS.ByteString -> Map.Map Text Event
proc d =
let events = throwLeft $ mapM procOne (filter (not . BS.null) $ BS.lines d)
in Map.fromListWith (<>) events
render ((time, rid), r) =
case r of
(StartAndTerminate (StartEvent {..}, TerminateEvent {..})) ->
( "\n"
<> squareBrackets
( BS.pack (show status)
`space` text rid
`space` (time $ endTime `diffUTCTime` startTime)
`space` ("pid=" <> (text . T.pack $ show pid))
`space` ("ppid=" <> (text . T.pack $ show ppid))
)
)
<> "\n> "
<> text executable
<> "\n "
<> text (T.intercalate "\n " . V.toList $ V.drop 1 arguments)
_ -> error "wtf" -- BS.pack $ show r
where
text = BS.fromStrict . T.encodeUtf8
squareBrackets s = "[" <> s <> "]"
space a b = a <> " " <> b
millisecond = 0.001
msec t = nominalDiffTimeToSeconds t / millisecond
time t = BS.pack $ (showFixed True $ msec t) <> " ms"
main :: IO ()
main = do
[file] <- getArgs
f <- BS.readFile file
let mapped = proc f
mappedWithKeys = Map.foldlWithKey' rekey Map.empty mapped
mapM_ (BS.putStrLn . render) $ Map.toList mappedWithKeys
where
rekey m k v = case v of
StartAndTerminate (s, t) -> m <> Map.singleton (startTime s, k) v
_ -> m