Merge pull request #217242 from maralorn/broken-reasons

maintainers/scripts/haskell/hydra-report: Add comments with error causes to broken list

authored by

maralorn and committed by
GitHub
994e845b 2827717f

+71 -31
+71 -31
maintainers/scripts/haskell/hydra-report.hs
··· 26 {-# LANGUAGE TupleSections #-} 27 {-# LANGUAGE ViewPatterns #-} 28 {-# OPTIONS_GHC -Wall #-} 29 30 import Control.Monad (forM_, (<=<)) 31 import Control.Monad.Trans (MonadIO (liftIO)) ··· 54 import Data.Time.Clock (UTCTime) 55 import GHC.Generics (Generic) 56 import Network.HTTP.Req ( 57 - GET (GET), 58 - NoReqBody (NoReqBody), 59 - defaultHttpConfig, 60 - header, 61 - https, 62 - jsonResponse, 63 - req, 64 - responseBody, 65 - responseTimeout, 66 - runReq, 67 - (/:), 68 ) 69 import System.Directory (XdgDirectory (XdgCache), getXdgDirectory) 70 import System.Environment (getArgs) ··· 76 import qualified Data.IntMap.Strict as IntMap 77 import qualified Data.IntSet as IntSet 78 import Data.Bifunctor (second) 79 80 newtype JobsetEvals = JobsetEvals 81 { evals :: Seq Eval ··· 123 124 getBuildReports :: IO () 125 getBuildReports = runReq defaultHttpConfig do 126 - evalMay <- Seq.lookup 0 . evals <$> myReq (https "hydra.nixos.org" /: "jobset" /: "nixpkgs" /: "haskell-updates" /: "evals") mempty 127 eval@Eval{id} <- maybe (liftIO $ fail "No Evalution found") pure evalMay 128 liftIO . putStrLn $ "Fetching evaluation " <> show id <> " from Hydra. This might take a few minutes..." 129 - buildReports :: Seq Build <- myReq (https "hydra.nixos.org" /: "eval" /: showT id /: "builds") (responseTimeout 600000000) 130 liftIO do 131 fileName <- reportFileName 132 putStrLn $ "Finished fetching all builds from Hydra, saving report as " <> fileName 133 now <- getCurrentTime 134 encodeFile fileName (eval, now, buildReports) 135 - where 136 - myReq query option = responseBody <$> req GET query NoReqBody jsonResponse (header "User-Agent" "hydra-report.hs/v1 (nixpkgs;maintainers/scripts/haskell)" <> option) 137 138 hydraEvalCommand :: FilePath 139 hydraEvalCommand = "hydra-eval-jobs" ··· 326 instance Foldable (Table row col) where 327 foldMap f (Table a) = foldMap f a 328 329 buildSummary :: MaintainerMap -> ReverseDependencyMap -> Seq Build -> StatusSummary 330 buildSummary maintainerMap reverseDependencyMap = foldl (Map.unionWith unionSummary) Map.empty . fmap toSummary 331 where 332 unionSummary (SummaryEntry (Table lb) lm lr lu) (SummaryEntry (Table rb) rm rr ru) = SummaryEntry (Table $ Map.union lb rb) (lm <> rm) (max lr rr) (max lu ru) 333 - toSummary Build{finished, buildstatus, job, id, system} = Map.singleton name (SummaryEntry (Table (Map.singleton (set, Platform system) (BuildResult state id))) maintainers reverseDeps unbrokenReverseDeps) 334 where 335 - state :: BuildState 336 - state = case (finished, buildstatus) of 337 - (0, _) -> Unfinished 338 - (_, Just 0) -> Success 339 - (_, Just 1) -> Failed 340 - (_, Just 2) -> DependencyFailed 341 - (_, Just 3) -> HydraFailure 342 - (_, Just 4) -> Canceled 343 - (_, Just 7) -> TimedOut 344 - (_, Just 11) -> OutputLimitExceeded 345 - (_, i) -> Unknown i 346 packageName = fromMaybe job (Text.stripSuffix ("." <> system) job) 347 splitted = nonEmpty $ Text.splitOn "." packageName 348 name = maybe packageName NonEmpty.last splitted ··· 486 487 printMarkBrokenList :: IO () 488 printMarkBrokenList = do 489 - (_, _, buildReport) <- readBuildReports 490 - forM_ buildReport \Build{buildstatus, job} -> 491 - case (buildstatus, Text.splitOn "." job) of 492 - (Just 1, ["haskellPackages", name, "x86_64-linux"]) -> putStrLn $ " - " <> Text.unpack name 493 _ -> pure ()
··· 26 {-# LANGUAGE TupleSections #-} 27 {-# LANGUAGE ViewPatterns #-} 28 {-# OPTIONS_GHC -Wall #-} 29 + {-# LANGUAGE DataKinds #-} 30 31 import Control.Monad (forM_, (<=<)) 32 import Control.Monad.Trans (MonadIO (liftIO)) ··· 55 import Data.Time.Clock (UTCTime) 56 import GHC.Generics (Generic) 57 import Network.HTTP.Req ( 58 + GET (GET), 59 + HttpResponse (HttpResponseBody), 60 + NoReqBody (NoReqBody), 61 + Option, 62 + Req, 63 + Scheme (Https), 64 + bsResponse, 65 + defaultHttpConfig, 66 + header, 67 + https, 68 + jsonResponse, 69 + req, 70 + responseBody, 71 + responseTimeout, 72 + runReq, 73 + (/:), 74 ) 75 import System.Directory (XdgDirectory (XdgCache), getXdgDirectory) 76 import System.Environment (getArgs) ··· 82 import qualified Data.IntMap.Strict as IntMap 83 import qualified Data.IntSet as IntSet 84 import Data.Bifunctor (second) 85 + import Data.Data (Proxy) 86 + import Data.ByteString (ByteString) 87 + import qualified Data.ByteString.Char8 as ByteString 88 + import Distribution.Simple.Utils (safeLast, fromUTF8BS) 89 90 newtype JobsetEvals = JobsetEvals 91 { evals :: Seq Eval ··· 133 134 getBuildReports :: IO () 135 getBuildReports = runReq defaultHttpConfig do 136 + evalMay <- Seq.lookup 0 . evals <$> hydraJSONQuery mempty ["jobset", "nixpkgs", "haskell-updates", "evals"] 137 eval@Eval{id} <- maybe (liftIO $ fail "No Evalution found") pure evalMay 138 liftIO . putStrLn $ "Fetching evaluation " <> show id <> " from Hydra. This might take a few minutes..." 139 + buildReports :: Seq Build <- hydraJSONQuery (responseTimeout 600000000) ["eval", showT id, "builds"] 140 liftIO do 141 fileName <- reportFileName 142 putStrLn $ "Finished fetching all builds from Hydra, saving report as " <> fileName 143 now <- getCurrentTime 144 encodeFile fileName (eval, now, buildReports) 145 + 146 + hydraQuery :: HttpResponse a => Proxy a -> Option 'Https -> [Text] -> Req (HttpResponseBody a) 147 + hydraQuery responseType option query = 148 + responseBody 149 + <$> req 150 + GET 151 + (foldl' (/:) (https "hydra.nixos.org") query) 152 + NoReqBody 153 + responseType 154 + (header "User-Agent" "hydra-report.hs/v1 (nixpkgs;maintainers/scripts/haskell)" <> option) 155 + 156 + hydraJSONQuery :: FromJSON a => Option 'Https -> [Text] -> Req a 157 + hydraJSONQuery = hydraQuery jsonResponse 158 + 159 + hydraPlainQuery :: [Text] -> Req ByteString 160 + hydraPlainQuery = hydraQuery bsResponse mempty 161 162 hydraEvalCommand :: FilePath 163 hydraEvalCommand = "hydra-eval-jobs" ··· 350 instance Foldable (Table row col) where 351 foldMap f (Table a) = foldMap f a 352 353 + getBuildState :: Build -> BuildState 354 + getBuildState Build{finished, buildstatus} = case (finished, buildstatus) of 355 + (0, _) -> Unfinished 356 + (_, Just 0) -> Success 357 + (_, Just 1) -> Failed 358 + (_, Just 2) -> DependencyFailed 359 + (_, Just 3) -> HydraFailure 360 + (_, Just 4) -> Canceled 361 + (_, Just 7) -> TimedOut 362 + (_, Just 11) -> OutputLimitExceeded 363 + (_, i) -> Unknown i 364 + 365 buildSummary :: MaintainerMap -> ReverseDependencyMap -> Seq Build -> StatusSummary 366 buildSummary maintainerMap reverseDependencyMap = foldl (Map.unionWith unionSummary) Map.empty . fmap toSummary 367 where 368 unionSummary (SummaryEntry (Table lb) lm lr lu) (SummaryEntry (Table rb) rm rr ru) = SummaryEntry (Table $ Map.union lb rb) (lm <> rm) (max lr rr) (max lu ru) 369 + toSummary build@Build{job, id, system} = Map.singleton name (SummaryEntry (Table (Map.singleton (set, Platform system) (BuildResult (getBuildState build) id))) maintainers reverseDeps unbrokenReverseDeps) 370 where 371 packageName = fromMaybe job (Text.stripSuffix ("." <> system) job) 372 splitted = nonEmpty $ Text.splitOn "." packageName 373 name = maybe packageName NonEmpty.last splitted ··· 511 512 printMarkBrokenList :: IO () 513 printMarkBrokenList = do 514 + (_, fetchTime, buildReport) <- readBuildReports 515 + runReq defaultHttpConfig $ forM_ buildReport \build@Build{job, id} -> 516 + case (getBuildState build, Text.splitOn "." job) of 517 + (Failed, ["haskellPackages", name, "x86_64-linux"]) -> do 518 + -- Fetch build log from hydra to figure out the cause of the error. 519 + build_log <- ByteString.lines <$> hydraPlainQuery ["build", showT id, "nixlog", "1", "raw"] 520 + -- We use the last probable error cause found in the build log file. 521 + let error_message = fromMaybe " failure " $ safeLast $ mapMaybe probableErrorCause build_log 522 + liftIO $ putStrLn $ " - " <> Text.unpack name <> " # " <> error_message <> " in job https://hydra.nixos.org/build/" <> show id <> " at " <> formatTime defaultTimeLocale "%Y-%m-%d" fetchTime 523 _ -> pure () 524 + 525 + {- | This function receives a line from a Nix Haskell builder build log and returns a possible error cause. 526 + | We might need to add other causes in the future if errors happen in unusual parts of the builder. 527 + -} 528 + probableErrorCause :: ByteString -> Maybe String 529 + probableErrorCause "Setup: Encountered missing or private dependencies:" = Just "dependency missing" 530 + probableErrorCause "running tests" = Just "test failure" 531 + probableErrorCause build_line | ByteString.isPrefixOf "Building" build_line = Just ("failure building " <> fromUTF8BS (fst $ ByteString.breakSubstring " for" $ ByteString.drop 9 build_line)) 532 + probableErrorCause build_line | ByteString.isSuffixOf "Phase" build_line = Just ("failure in " <> fromUTF8BS build_line) 533 + probableErrorCause _ = Nothing