1 {-| Solver for N+1 cluster errors
5 module Main (main) where
12 import System.Console.GetOpt
13 import qualified System
15 import Text.Printf (printf)
17 import qualified Container
18 import qualified Cluster
19 import qualified Version
23 -- | Command line options structure.
24 data Options = Options
25 { optShowNodes :: Bool
28 , optNodef :: FilePath
29 , optInstf :: FilePath
34 -- | Default values for the command line options.
35 defaultOptions :: Options
36 defaultOptions = Options
37 { optShowNodes = False
41 , optInstf = "instances"
46 {- | Start computing the solution at the given depth and recurse until
47 we find a valid solution or we exceed the maximum depth.
50 iterateDepth :: Cluster.Table -- ^ The starting table
51 -> Int -- ^ Remaining length
52 -> [(Int, String)] -- ^ Node idx to name list
53 -> [(Int, String)] -- ^ Inst idx to name list
54 -> Int -- ^ Max node name len
55 -> Int -- ^ Max instance name len
56 -> [[String]] -- ^ Current command list
57 -> Bool -- ^ Wheter to be silent
58 -> IO (Cluster.Table, [[String]]) -- ^ The resulting table and
60 iterateDepth ini_tbl max_rounds ktn kti nmlen imlen cmd_strs oneline =
61 let Cluster.Table ini_nl ini_il ini_cv ini_plc = ini_tbl
62 all_inst = Container.elems ini_il
63 node_idx = Container.keys ini_nl
64 fin_tbl = Cluster.checkMove node_idx ini_tbl all_inst
65 (Cluster.Table _ _ fin_cv fin_plc) = fin_tbl
66 ini_plc_len = length ini_plc
67 fin_plc_len = length fin_plc
68 allowed_next = (max_rounds < 0 || length fin_plc < max_rounds)
72 (sol_line, cmds) = Cluster.printSolutionLine ini_il ktn kti
73 nmlen imlen (head fin_plc)
74 upd_cmd_strs = cmds:cmd_strs
75 unless (oneline || fin_plc_len == ini_plc_len) $ do
78 (if fin_cv < ini_cv then -- this round made success, try deeper
80 then iterateDepth fin_tbl max_rounds ktn kti
81 nmlen imlen upd_cmd_strs oneline
82 -- don't go deeper, but return the better solution
83 else return (fin_tbl, upd_cmd_strs)
85 return (ini_tbl, cmd_strs))
87 -- | Options list and functions
88 options :: [OptDescr (Options -> Options)]
90 [ Option ['p'] ["print-nodes"]
91 (NoArg (\ opts -> opts { optShowNodes = True }))
92 "print the final node list"
93 , Option ['C'] ["print-commands"]
94 (NoArg (\ opts -> opts { optShowCmds = True }))
95 "print the ganeti command list for reaching the solution"
96 , Option ['o'] ["oneline"]
97 (NoArg (\ opts -> opts { optOneline = True }))
98 "print the ganeti command list for reaching the solution"
99 , Option ['n'] ["nodes"]
100 (ReqArg (\ f opts -> opts { optNodef = f }) "FILE")
102 , Option ['i'] ["instances"]
103 (ReqArg (\ f opts -> opts { optInstf = f }) "FILE")
104 "the instance list FILE"
105 , Option ['m'] ["master"]
106 (ReqArg (\ m opts -> opts { optMaster = m }) "ADDRESS")
107 "collect data via RAPI at the given ADDRESS"
108 , Option ['l'] ["max-length"]
109 (ReqArg (\ i opts -> opts { optMaxLength = (read i)::Int }) "N")
110 "cap the solution at this many moves (useful for very unbalanced \
114 -- | Command line parser, using the 'options' structure.
115 parseOpts :: [String] -> IO (Options, [String])
117 case getOpt Permute options argv of
119 return (foldl (flip id) defaultOptions o, n)
121 ioError (userError (concat errs ++ usageInfo header options))
122 where header = printf "hbal %s\nUsage: hbal [OPTION...]"
128 cmd_args <- System.getArgs
129 (opts, _) <- parseOpts cmd_args
131 let oneline = optOneline opts
132 let (node_data, inst_data) =
133 case optMaster opts of
134 "" -> (readFile $ optNodef opts,
135 readFile $ optInstf opts)
136 host -> (readData getNodes host,
137 readData getInstances host)
139 (nl, il, csf, ktn, kti) <- liftM2 Cluster.loadData node_data inst_data
141 unless oneline $ printf "Loaded %d nodes, %d instances\n"
145 when (length csf > 0 && not oneline) $ do
146 printf "Note: Stripping common suffix of '%s' from names\n" csf
148 let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il
149 unless oneline $ printf
150 "Initial check done: %d bad nodes, %d bad instances.\n"
151 (length bad_nodes) (length bad_instances)
153 when (length bad_nodes > 0) $ do
154 putStrLn "Cluster is not N+1 happy, continuing but no guarantee \
155 \that the cluster will end N+1 happy."
157 when (optShowNodes opts) $
159 putStrLn "Initial cluster status:"
160 putStrLn $ Cluster.printNodes ktn nl
162 let ini_cv = Cluster.compCV nl
163 ini_tbl = Cluster.Table nl il ini_cv []
164 unless oneline $ printf "Initial coefficients: overall %.8f, %s\n"
165 ini_cv (Cluster.printStats nl)
167 unless oneline $ putStrLn "Trying to minimize the CV..."
168 let mlen_fn = maximum . (map length) . snd . unzip
172 (fin_tbl, cmd_strs) <- iterateDepth ini_tbl (optMaxLength opts)
173 ktn kti nmlen imlen [] oneline
174 let (Cluster.Table fin_nl _ fin_cv fin_plc) = fin_tbl
175 ord_plc = reverse fin_plc
178 then printf "No solution found\n"
179 else printf "Final coefficients: overall %.8f, %s\n"
180 fin_cv (Cluster.printStats fin_nl))
182 unless oneline $ printf "Solution length=%d\n" (length ord_plc)
184 when (optShowCmds opts) $
187 putStrLn "Commands to run to reach the above solution:"
188 putStr . Cluster.formatCmds . reverse $ cmd_strs
189 when (optShowNodes opts) $
191 let (orig_mem, orig_disk) = Cluster.totalResources nl
192 (final_mem, final_disk) = Cluster.totalResources fin_nl
194 putStrLn "Final cluster status:"
195 putStrLn $ Cluster.printNodes ktn fin_nl
196 printf "Original: mem=%d disk=%d\n" orig_mem orig_disk
197 printf "Final: mem=%d disk=%d\n" final_mem final_disk
199 printf "%.8f %d %.8f %8.3f\n"
200 ini_cv (length ord_plc) fin_cv (ini_cv / fin_cv)