Change the tryAlloc/tryReloc workflow
[ganeti-local] / hspace.hs
index ae83c35..ce09d5c 100644 (file)
--- a/hspace.hs
+++ b/hspace.hs
@@ -25,6 +25,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
 module Main (main) where
 
+import Data.Char (toUpper)
 import Data.List
 import Data.Function
 import Monad
@@ -147,37 +148,49 @@ options =
       "show help"
     ]
 
--- | Build failure stats out of a list of failure reasons
-concatFailure :: [(FailMode, Int)] -> FailMode -> [(FailMode, Int)]
-concatFailure flst reason =
-    let cval = lookup reason flst
-    in case cval of
-         Nothing -> (reason, 1):flst
-         Just val -> let plain = filter (\(x, _) -> x /= reason) flst
-                     in (reason, val+1):plain
-
--- | Build list of failures and placements out of an list of possible
--- | allocations
-filterFails :: Cluster.AllocSolution
-            -> ([(FailMode, Int)],
-                [(Node.List, Instance.Instance, [Node.Node])])
-filterFails sols =
-    let (alst, blst) = unzip . map (\ (onl, i, nn) ->
-                                        case onl of
-                                          OpFail reason -> ([reason], [])
-                                          OpGood gnl -> ([], [(gnl, i, nn)])
-                                   ) $ sols
-        aval = concat alst
-        bval = concat blst
-    in (foldl' concatFailure [] aval, bval)
-
--- | Get the placement with best score out of a list of possible placements
-processResults :: [(Node.List, Instance.Instance, [Node.Node])]
-               -> (Node.List, Instance.Instance, [Node.Node])
-processResults sols =
-    let sols' = map (\e@(nl', _, _) -> (Cluster.compCV  nl', e)) sols
-        sols'' = sortBy (compare `on` fst) sols'
-    in snd $ head sols''
+data Phase = PInitial | PFinal
+
+statsData :: [(String, Cluster.CStats -> String)]
+statsData = [ ("SCORE", printf "%.8f" . Cluster.cs_score)
+            , ("INST_CNT", printf "%d" . Cluster.cs_ninst)
+            , ("MEM_FREE", printf "%d" . Cluster.cs_fmem)
+            , ("MEM_AVAIL", printf "%d" . Cluster.cs_amem)
+            , ("MEM_RESVD",
+               \cs -> printf "%d" (Cluster.cs_fmem cs - Cluster.cs_amem cs))
+            , ("MEM_INST", printf "%d" . Cluster.cs_imem)
+            , ("MEM_OVERHEAD",
+               \cs -> printf "%d" (Cluster.cs_xmem cs + Cluster.cs_nmem cs))
+            , ("MEM_EFF",
+               \cs -> printf "%.8f" (fromIntegral (Cluster.cs_imem cs) /
+                                     Cluster.cs_tmem cs))
+            , ("DSK_FREE", printf "%d" . Cluster.cs_fdsk)
+            , ("DSK_AVAIL", printf "%d ". Cluster.cs_adsk)
+            , ("DSK_RESVD",
+               \cs -> printf "%d" (Cluster.cs_fdsk cs - Cluster.cs_adsk cs))
+            , ("DSK_INST", printf "%d" . Cluster.cs_idsk)
+            , ("DSK_EFF",
+               \cs -> printf "%.8f" (fromIntegral (Cluster.cs_idsk cs) /
+                                    Cluster.cs_tdsk cs))
+            , ("CPU_INST", printf "%d" . Cluster.cs_icpu)
+            , ("CPU_EFF",
+               \cs -> printf "%.8f" (fromIntegral (Cluster.cs_icpu cs) /
+                                     Cluster.cs_tcpu cs))
+            , ("MNODE_MEM_AVAIL", printf "%d" . Cluster.cs_mmem)
+            , ("MNODE_DSK_AVAIL", printf "%d" . Cluster.cs_mdsk)
+            ]
+
+specData :: [(String, Options -> String)]
+specData = [ ("MEM", printf "%d" . optIMem)
+           , ("DSK", printf "%d" . optIDsk)
+           , ("CPU", printf "%d" . optIVCPUs)
+           , ("RQN", printf "%d" . optINodes)
+           ]
+
+clusterData :: [(String, Cluster.CStats -> String)]
+clusterData = [ ("MEM", printf "%.0f" . Cluster.cs_tmem)
+              , ("DSK", printf "%.0f" . Cluster.cs_tdsk)
+              , ("CPU", printf "%.0f" . Cluster.cs_tcpu)
+              ]
 
 -- | Recursively place instances on the cluster until we're out of space
 iterateDepth :: Node.List
@@ -195,26 +208,19 @@ iterateDepth nl il newinst nreq ixes =
                  OpResult Cluster.AllocSolution
       in case sols of
            OpFail _ -> ([], nl, ixes)
-           OpGood sols' ->
-               let (errs, sols3) = filterFails sols'
-               in if null sols3
-                  then (errs, nl, ixes)
-                  else let (xnl, xi, _) = processResults sols3
-                       in iterateDepth xnl il newinst nreq (xi:ixes)
+           OpGood (errs, _, sols3) ->
+               case sols3 of
+                 Nothing -> (Cluster.collapseFailures errs, nl, ixes)
+                 Just (_, (xnl, xi, _)) ->
+                     iterateDepth xnl il newinst nreq $! (xi:ixes)
 
 -- | Function to print stats for a given phase
-printStats :: String -> Cluster.CStats -> IO ()
-printStats kind cs = do
-  printf "%s free RAM: %d\n" kind (Cluster.cs_fmem cs)
-  printf "%s allocatable RAM: %d\n" kind (Cluster.cs_amem cs)
-  printf "%s reserved RAM: %d\n" kind (Cluster.cs_fmem cs -
-                                       Cluster.cs_amem cs)
-  printf "%s free disk: %d\n" kind (Cluster.cs_fdsk cs)
-  printf "%s allocatable disk: %d\n" kind (Cluster.cs_adsk cs)
-  printf "%s reserved disk: %d\n" kind (Cluster.cs_fdsk cs -
-                                        Cluster.cs_adsk cs)
-  printf "%s max node allocatable RAM: %d\n" kind (Cluster.cs_mmem cs)
-  printf "%s max node allocatable disk: %d\n" kind (Cluster.cs_mdsk cs)
+printStats :: Phase -> Cluster.CStats -> [(String, String)]
+printStats ph cs =
+  map (\(s, fn) -> (printf "%s_%s" kind s, fn cs)) statsData
+  where kind = case ph of
+                 PInitial -> "INI"
+                 PFinal -> "FIN"
 
 -- | Print final stats and related metrics
 printResults :: Node.List -> Int -> Int -> [(FailMode, Int)] -> IO ()
@@ -222,14 +228,28 @@ printResults fin_nl num_instances allocs sreason = do
   let fin_stats = Cluster.totalResources fin_nl
       fin_instances = num_instances + allocs
 
-  printf "Final score: %.8f\n" (Cluster.compCV fin_nl)
-  printf "Final instances: %d\n" (num_instances + allocs)
-  printStats "Final" fin_stats
-  printf "Usage: %.5f\n" ((fromIntegral num_instances::Double) /
-                          fromIntegral fin_instances)
-  printf "Allocations: %d\n" allocs
-  putStr (unlines . map (\(x, y) -> printf "%s: %d" (show x) y) $ sreason)
-  printf "Most likely fail reason: %s\n" (show . fst . head $ sreason)
+  when (num_instances + allocs /= Cluster.cs_ninst fin_stats) $
+       do
+         hPrintf stderr "ERROR: internal inconsistency, allocated (%d)\
+                        \ != counted (%d)\n" (num_instances + allocs)
+                                 (Cluster.cs_ninst fin_stats)
+         exitWith $ ExitFailure 1
+
+  printKeys $ printStats PFinal fin_stats
+  printKeys [ ("ALLOC_USAGE", printf "%.8f"
+                                ((fromIntegral num_instances::Double) /
+                                 fromIntegral fin_instances))
+            , ("ALLOC_COUNT", printf "%d" allocs)
+            , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ head sreason)
+            ]
+  printKeys $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x),
+                               printf "%d" y)) sreason
+  -- this should be the final entry
+  printKeys [("OK", "1")]
+
+-- | Format a list of key/values as a shell fragment
+printKeys :: [(String, String)] -> IO ()
+printKeys = mapM_ (\(k, v) -> printf "HTS_%s=%s\n" (map toUpper k) v)
 
 -- | Main function.
 main :: IO ()
@@ -245,10 +265,7 @@ main = do
 
   (fixed_nl, il, csf) <- CLI.loadExternalData opts
 
-  printf "Spec RAM: %d\n" (optIMem opts)
-  printf "Spec disk: %d\n" (optIDsk opts)
-  printf "Spec CPUs: %d\n" (optIVCPUs opts)
-  printf "Spec nodes: %d\n" (optINodes opts)
+  printKeys $ map (\(a, fn) -> ("SPEC_" ++ a, fn opts)) specData
 
   let num_instances = length $ Container.elems il
 
@@ -279,23 +296,23 @@ main = do
            nm
 
   when (length csf > 0 && verbose > 1) $
-       printf "Note: Stripping common suffix of '%s' from names\n" csf
+       hPrintf stderr "Note: Stripping common suffix of '%s' from names\n" csf
 
   when (optShowNodes opts) $
        do
-         putStrLn "Initial cluster status:"
-         putStrLn $ Cluster.printNodes nl
+         hPutStrLn stderr "Initial cluster status:"
+         hPutStrLn stderr $ Cluster.printNodes nl
 
   let ini_cv = Cluster.compCV nl
       ini_stats = Cluster.totalResources nl
 
-  (if verbose > 2 then
-       printf "Initial coefficients: overall %.8f, %s\n"
-       ini_cv (Cluster.printStats nl)
-   else
-       printf "Initial score: %.8f\n" ini_cv)
-  printf "Initial instances: %d\n" num_instances
-  printStats "Initial" ini_stats
+  when (verbose > 2) $ do
+         hPrintf stderr "Initial coefficients: overall %.8f, %s\n"
+                 ini_cv (Cluster.printStats nl)
+
+  printKeys $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats)) clusterData
+  printKeys [("CLUSTER_NODES", printf "%d" (length all_nodes))]
+  printKeys $ printStats PInitial ini_stats
 
   let bad_nodes = fst $ Cluster.computeBadItems nl il
   when (length bad_nodes > 0) $ do
@@ -314,19 +331,20 @@ main = do
       ix_namelen = maximum . map (length . Instance.name) $ fin_ixes
       sreason = reverse $ sortBy (compare `on` snd) ereason
 
-  printResults fin_nl num_instances allocs sreason
-
   when (verbose > 1) $
-         putStr . unlines . map (\i -> printf "Inst: %*s %-*s %-*s"
-                     ix_namelen (Instance.name i)
-                     nmlen (Container.nameOf fin_nl $ Instance.pnode i)
-                     nmlen (let sdx = Instance.snode i
-                            in if sdx == Node.noSecondary then ""
-                               else Container.nameOf fin_nl sdx))
-         $ fin_ixes
+         hPutStr stderr . unlines $
+         map (\i -> printf "Inst: %*s %-*s %-*s"
+                    ix_namelen (Instance.name i)
+                    nmlen (Container.nameOf fin_nl $ Instance.pnode i)
+                    nmlen (let sdx = Instance.snode i
+                           in if sdx == Node.noSecondary then ""
+                              else Container.nameOf fin_nl sdx)
+             ) fin_ixes
 
   when (optShowNodes opts) $
        do
-         putStrLn ""
-         putStrLn "Final cluster status:"
-         putStrLn $ Cluster.printNodes fin_nl
+         hPutStrLn stderr ""
+         hPutStrLn stderr "Final cluster status:"
+         hPutStrLn stderr $ Cluster.printNodes fin_nl
+
+  printResults fin_nl num_instances allocs sreason