module HSBencher.Backend.Codespeed
(
defaultCodespeedPlugin
, CodespeedConfig(..)
, CodespeedPlug(), CodespeedCmdLnFlag(..),
)
where
import Control.Monad.Reader
import Control.Concurrent (threadDelay)
import qualified Control.Exception as E
import Data.Maybe (isJust, fromJust, catMaybes, fromMaybe)
import Data.Dynamic
import Data.Default (Default(..))
import qualified Data.Set as S
import qualified Data.Map as M
import qualified Data.List as L
import qualified Data.ByteString.Lazy.Char8 as B
import qualified Data.ByteString.Char8 as BS
import Data.Time.Clock
import Data.Time.Calendar
import Data.Time.Format ()
import Network.HTTP.Types (renderQuery, urlEncode, urlDecode)
import Network.HTTP (simpleHTTP, postRequestWithBody)
import Control.Monad.Trans.Resource (runResourceT)
import Text.JSON
import HSBencher.Types
import HSBencher.Internal.Logging (log)
import Prelude hiding (log)
import System.IO (hPutStrLn, stderr)
import System.IO.Unsafe (unsafePerformIO)
import System.Console.GetOpt (getOpt, ArgOrder(Permute), OptDescr(Option), ArgDescr(..), usageInfo)
import System.Directory (doesFileExist, doesDirectoryExist, getAppUserDataDirectory,
createDirectory, renameFile, removeFile)
import System.FilePath ((</>),(<.>), splitExtension)
import System.IO.Unsafe (unsafePerformIO)
import System.Environment (getEnvironment)
import System.Exit
import Control.Concurrent.MVar
defaultCodespeedPlugin :: CodespeedPlug
defaultCodespeedPlugin = CodespeedPlug
instance Default CodespeedPlug where
def = defaultCodespeedPlugin
data CodespeedConfig =
CodespeedConfig { codespeedURL :: URL
, projName :: String }
deriving (Show,Read,Ord,Eq, Typeable)
instance Default CodespeedConfig where
def = CodespeedConfig
{ codespeedURL = error "incomplete CodespeedConfig: Must set Codespeed URL (--codespeed) to use this plugin!"
, projName = error "incomplete CodespeedConfig: Must set Codespeed --projname to use this plugin!"
}
data CodespeedCmdLnFlag = CodespeedURL URL
| CodespeedProjName String
deriving (Show,Read,Ord,Eq, Typeable)
type URL = String
getDateTime :: IO String
getDateTime = do
utc <- getCurrentTime
return $ show utc
uploadBenchResult :: BenchmarkResult -> BenchM ()
uploadBenchResult br = do
lift$ putStrLn " [codespeed] Begin upload of one benchmark result."
conf <- ask
let codespeedConfig = getMyConf CodespeedPlug conf
lift$ putStrLn$ " [codespeed] Running with plugin config: \n"++show codespeedConfig
let CodespeedConfig {codespeedURL} = codespeedConfig
contentType = "application/x-www-form-urlencoded"
addURL = (codespeedURL ++ "/result/add/json/")
let json = renderJSONResult codespeedConfig br
bod = urlEncode False $ BS.pack json
let req = postRequestWithBody addURL contentType $ BS.unpack bod
lift$ putStrLn$ " [codespeed] Uploading json: "++ json
lift$ putStrLn$ " [codespeed] URl-encoded json POST body: "++ BS.unpack bod
lift$ putStrLn$ " [codespeed] Submitting HTTP Post request: \n"++show req
resp <- lift$ simpleHTTP req
case resp of
Left err -> lift$ putStrLn$ " [codespeed] ERROR uploading: \n"++show err
Right x -> lift$ putStrLn$ " [codespeed] Got response from server:\n"++show x
return ()
renderJSONResult :: CodespeedConfig -> BenchmarkResult -> String
renderJSONResult CodespeedConfig{projName} benchRes =
simpleFormat
[
("project", S projName)
, ("executable", S exec)
, ("benchmark", S bench)
, ("commitid", S _GIT_HASH)
, ("environment", S _HOSTNAME)
, ("result_value", D _MEDIANTIME)
, ("branch", S _GIT_BRANCH)
, ("max", D _MAXTIME)
, ("min", D _MINTIME)
]
where
BenchmarkResult{..} = benchRes
exec = combine $ [_VARIANT] ++ if _THREADS==0 then []
else [show _THREADS ++ "T"]
bench = combine [_PROGNAME, unwords _ARGS]
combine :: [String] -> String
combine fields =
let fields' = filter (not . null) fields in
L.concat (L.intersperse "|" fields')
data RHS = S String | D Double
simpleFormat :: [(String,RHS)] -> String
simpleFormat prs = "json=[{" ++ bod ++"}]"
where
bod = L.concat $ L.intersperse ", " $ L.map fn prs
fn (l,r) = show l ++ ": " ++ rhs r
rhs (S s) = show s
rhs (D d) = show d
data CodespeedPlug = CodespeedPlug
deriving (Eq,Show,Ord,Read)
instance Plugin CodespeedPlug where
type PlugConf CodespeedPlug = CodespeedConfig
type PlugFlag CodespeedPlug = CodespeedCmdLnFlag
plugName _ = "codespeed"
plugCmdOpts _ = codespeed_cli_options
plugUploadRow p cfg row = runReaderT (uploadBenchResult row) cfg
plugInitialize p gconf = do
putStrLn " [codespeed] Codespeed table plugin initializing.. (which is a NOOP)"
return gconf
foldFlags p flgs cnf0 =
foldr ($) cnf0 (map doFlag flgs)
where
doFlag (CodespeedURL url) r = r { codespeedURL = url}
doFlag (CodespeedProjName nm) r = r { projName = nm }
theEnv :: [(String,String)]
theEnv = unsafePerformIO getEnvironment
codespeed_cli_options :: (String, [OptDescr CodespeedCmdLnFlag])
codespeed_cli_options =
("Codespeed Table Options:",
[ Option [] ["codespeed"] (ReqArg CodespeedURL "URL")
"specify the root URL of the Codespeed installation"
, Option [] ["projname"] (ReqArg CodespeedProjName "NAME")
"specify which Codespeed Project receives the uploaded results"
])