1 {-| Cluster space sizing
7 Copyright (C) 2009, 2010, 2011 Google Inc.
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 module Ganeti.HTools.Program.Hspace (main) where
29 import Data.Char (toUpper, isAlphaNum, toLower)
30 import Data.Function (on)
32 import Data.Ord (comparing)
35 import System.Environment (getArgs)
37 import Text.Printf (printf, hPrintf)
39 import qualified Ganeti.HTools.Container as Container
40 import qualified Ganeti.HTools.Cluster as Cluster
41 import qualified Ganeti.HTools.Node as Node
42 import qualified Ganeti.HTools.Instance as Instance
44 import Ganeti.HTools.Utils
45 import Ganeti.HTools.Types
46 import Ganeti.HTools.CLI
47 import Ganeti.HTools.ExtLoader
48 import Ganeti.HTools.Loader
50 -- | Options list and functions.
73 -- | The allocation phase we're in (initial, after tiered allocs, or
74 -- after regular allocation).
79 -- | The kind of instance spec we print.
80 data SpecType = SpecNormal
83 -- | What we prefix a spec with.
84 specPrefix :: SpecType -> String
85 specPrefix SpecNormal = "SPEC"
86 specPrefix SpecTiered = "TSPEC_INI"
88 -- | The description of a spec.
89 specDescription :: SpecType -> String
90 specDescription SpecNormal = "Standard (fixed-size)"
91 specDescription SpecTiered = "Tiered (initial size)"
93 -- | Efficiency generic function.
94 effFn :: (Cluster.CStats -> Integer)
95 -> (Cluster.CStats -> Double)
96 -> Cluster.CStats -> Double
97 effFn fi ft cs = fromIntegral (fi cs) / ft cs
99 -- | Memory efficiency.
100 memEff :: Cluster.CStats -> Double
101 memEff = effFn Cluster.csImem Cluster.csTmem
103 -- | Disk efficiency.
104 dskEff :: Cluster.CStats -> Double
105 dskEff = effFn Cluster.csIdsk Cluster.csTdsk
108 cpuEff :: Cluster.CStats -> Double
109 cpuEff = effFn Cluster.csIcpu (fromIntegral . Cluster.csVcpu)
111 -- | Holds data for converting a 'Cluster.CStats' structure into
112 -- detailed statictics.
113 statsData :: [(String, Cluster.CStats -> String)]
114 statsData = [ ("SCORE", printf "%.8f" . Cluster.csScore)
115 , ("INST_CNT", printf "%d" . Cluster.csNinst)
116 , ("MEM_FREE", printf "%d" . Cluster.csFmem)
117 , ("MEM_AVAIL", printf "%d" . Cluster.csAmem)
119 \cs -> printf "%d" (Cluster.csFmem cs - Cluster.csAmem cs))
120 , ("MEM_INST", printf "%d" . Cluster.csImem)
122 \cs -> printf "%d" (Cluster.csXmem cs + Cluster.csNmem cs))
123 , ("MEM_EFF", printf "%.8f" . memEff)
124 , ("DSK_FREE", printf "%d" . Cluster.csFdsk)
125 , ("DSK_AVAIL", printf "%d". Cluster.csAdsk)
127 \cs -> printf "%d" (Cluster.csFdsk cs - Cluster.csAdsk cs))
128 , ("DSK_INST", printf "%d" . Cluster.csIdsk)
129 , ("DSK_EFF", printf "%.8f" . dskEff)
130 , ("CPU_INST", printf "%d" . Cluster.csIcpu)
131 , ("CPU_EFF", printf "%.8f" . cpuEff)
132 , ("MNODE_MEM_AVAIL", printf "%d" . Cluster.csMmem)
133 , ("MNODE_DSK_AVAIL", printf "%d" . Cluster.csMdsk)
136 -- | List holding 'RSpec' formatting information.
137 specData :: [(String, RSpec -> String)]
138 specData = [ ("MEM", printf "%d" . rspecMem)
139 , ("DSK", printf "%d" . rspecDsk)
140 , ("CPU", printf "%d" . rspecCpu)
143 -- | List holding 'Cluster.CStats' formatting information.
144 clusterData :: [(String, Cluster.CStats -> String)]
145 clusterData = [ ("MEM", printf "%.0f" . Cluster.csTmem)
146 , ("DSK", printf "%.0f" . Cluster.csTdsk)
147 , ("CPU", printf "%.0f" . Cluster.csTcpu)
148 , ("VCPU", printf "%d" . Cluster.csVcpu)
151 -- | Function to print stats for a given phase.
152 printStats :: Phase -> Cluster.CStats -> [(String, String)]
154 map (\(s, fn) -> (printf "%s_%s" kind s, fn cs)) statsData
155 where kind = case ph of
160 -- | Print failure reason and scores
161 printFRScores :: Node.List -> Node.List -> [(FailMode, Int)] -> IO ()
162 printFRScores ini_nl fin_nl sreason = do
163 printf " - most likely failure reason: %s\n" $ failureReason sreason::IO ()
164 printClusterScores ini_nl fin_nl
165 printClusterEff (Cluster.totalResources fin_nl)
167 -- | Print final stats and related metrics.
168 printResults :: Bool -> Node.List -> Node.List -> Int -> Int
169 -> [(FailMode, Int)] -> IO ()
170 printResults True _ fin_nl num_instances allocs sreason = do
171 let fin_stats = Cluster.totalResources fin_nl
172 fin_instances = num_instances + allocs
174 when (num_instances + allocs /= Cluster.csNinst fin_stats) $
176 hPrintf stderr "ERROR: internal inconsistency, allocated (%d)\
177 \ != counted (%d)\n" (num_instances + allocs)
178 (Cluster.csNinst fin_stats) :: IO ()
179 exitWith $ ExitFailure 1
181 printKeys $ printStats PFinal fin_stats
182 printKeys [ ("ALLOC_USAGE", printf "%.8f"
183 ((fromIntegral num_instances::Double) /
184 fromIntegral fin_instances))
185 , ("ALLOC_INSTANCES", printf "%d" allocs)
186 , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ head sreason)
188 printKeys $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x),
189 printf "%d" y)) sreason
191 printResults False ini_nl fin_nl _ allocs sreason = do
192 putStrLn "Normal (fixed-size) allocation results:"
193 printf " - %3d instances allocated\n" allocs :: IO ()
194 printFRScores ini_nl fin_nl sreason
196 -- | Prints the final @OK@ marker in machine readable output.
197 printFinal :: Bool -> IO ()
199 -- this should be the final entry
200 printKeys [("OK", "1")]
202 printFinal False = return ()
204 -- | Compute the tiered spec counts from a list of allocated
206 tieredSpecMap :: [Instance.Instance]
208 tieredSpecMap trl_ixes =
209 let fin_trl_ixes = reverse trl_ixes
210 ix_byspec = groupBy ((==) `on` Instance.specOf) fin_trl_ixes
211 spec_map = map (\ixs -> (Instance.specOf $ head ixs, length ixs))
215 -- | Formats a spec map to strings.
216 formatSpecMap :: [(RSpec, Int)] -> [String]
218 map (\(spec, cnt) -> printf "%d,%d,%d=%d" (rspecMem spec)
219 (rspecDsk spec) (rspecCpu spec) cnt)
221 -- | Formats \"key-metrics\" values.
222 formatRSpec :: Double -> String -> RSpec -> [(String, String)]
223 formatRSpec m_cpu s r =
224 [ ("KM_" ++ s ++ "_CPU", show $ rspecCpu r)
225 , ("KM_" ++ s ++ "_NPU", show $ fromIntegral (rspecCpu r) / m_cpu)
226 , ("KM_" ++ s ++ "_MEM", show $ rspecMem r)
227 , ("KM_" ++ s ++ "_DSK", show $ rspecDsk r)
230 -- | Shows allocations stats.
231 printAllocationStats :: Double -> Node.List -> Node.List -> IO ()
232 printAllocationStats m_cpu ini_nl fin_nl = do
233 let ini_stats = Cluster.totalResources ini_nl
234 fin_stats = Cluster.totalResources fin_nl
235 (rini, ralo, runa) = Cluster.computeAllocationDelta ini_stats fin_stats
236 printKeys $ formatRSpec m_cpu "USED" rini
237 printKeys $ formatRSpec m_cpu "POOL"ralo
238 printKeys $ formatRSpec m_cpu "UNAV" runa
240 -- | Ensure a value is quoted if needed.
241 ensureQuoted :: String -> String
242 ensureQuoted v = if not (all (\c -> isAlphaNum c || c == '.') v)
246 -- | Format a list of key\/values as a shell fragment.
247 printKeys :: [(String, String)] -> IO ()
248 printKeys = mapM_ (\(k, v) ->
249 printf "HTS_%s=%s\n" (map toUpper k) (ensureQuoted v))
251 -- | Converts instance data to a list of strings.
252 printInstance :: Node.List -> Instance.Instance -> [String]
253 printInstance nl i = [ Instance.name i
254 , Container.nameOf nl $ Instance.pNode i
255 , let sdx = Instance.sNode i
256 in if sdx == Node.noSecondary then ""
257 else Container.nameOf nl sdx
258 , show (Instance.mem i)
259 , show (Instance.dsk i)
260 , show (Instance.vcpus i)
263 -- | Optionally print the allocation map.
264 printAllocationMap :: Int -> String
265 -> Node.List -> [Instance.Instance] -> IO ()
266 printAllocationMap verbose msg nl ixes =
267 when (verbose > 1) $ do
268 hPutStrLn stderr (msg ++ " map")
269 hPutStr stderr . unlines . map ((:) ' ' . unwords) $
270 formatTable (map (printInstance nl) (reverse ixes))
271 -- This is the numberic-or-not field
272 -- specification; the first three fields are
273 -- strings, whereas the rest are numeric
274 [False, False, False, True, True, True]
276 -- | Formats nicely a list of resources.
277 formatResources :: a -> [(String, a->String)] -> String
278 formatResources res =
279 intercalate ", " . map (\(a, fn) -> a ++ " " ++ fn res)
281 -- | Print the cluster resources.
282 printCluster :: Bool -> Cluster.CStats -> Int -> IO ()
283 printCluster True ini_stats node_count = do
284 printKeys $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats)) clusterData
285 printKeys [("CLUSTER_NODES", printf "%d" node_count)]
286 printKeys $ printStats PInitial ini_stats
288 printCluster False ini_stats node_count = do
289 printf "The cluster has %d nodes and the following resources:\n %s.\n"
290 node_count (formatResources ini_stats clusterData)::IO ()
291 printf "There are %s initial instances on the cluster.\n"
292 (if inst_count > 0 then show inst_count else "no" )
293 where inst_count = Cluster.csNinst ini_stats
295 -- | Prints the normal instance spec.
296 printISpec :: Bool -> RSpec -> SpecType -> DiskTemplate -> IO ()
297 printISpec True ispec spec disk_template = do
298 printKeys $ map (\(a, fn) -> (prefix ++ "_" ++ a, fn ispec)) specData
299 printKeys [ (prefix ++ "_RQN", printf "%d" req_nodes) ]
300 printKeys [ (prefix ++ "_DISK_TEMPLATE",
301 diskTemplateToRaw disk_template) ]
302 where req_nodes = Instance.requiredNodes disk_template
303 prefix = specPrefix spec
305 printISpec False ispec spec disk_template =
306 printf "%s instance spec is:\n %s, using disk\
308 (specDescription spec)
309 (formatResources ispec specData) (diskTemplateToRaw disk_template)
311 -- | Prints the tiered results.
312 printTiered :: Bool -> [(RSpec, Int)] -> Double
313 -> Node.List -> Node.List -> [(FailMode, Int)] -> IO ()
314 printTiered True spec_map m_cpu nl trl_nl _ = do
315 printKeys $ printStats PTiered (Cluster.totalResources trl_nl)
316 printKeys [("TSPEC", unwords (formatSpecMap spec_map))]
317 printAllocationStats m_cpu nl trl_nl
319 printTiered False spec_map _ ini_nl fin_nl sreason = do
320 _ <- printf "Tiered allocation results:\n"
321 mapM_ (\(ispec, cnt) ->
322 printf " - %3d instances of spec %s\n" cnt
323 (formatResources ispec specData)) spec_map
324 printFRScores ini_nl fin_nl sreason
326 -- | Displays the initial/final cluster scores.
327 printClusterScores :: Node.List -> Node.List -> IO ()
328 printClusterScores ini_nl fin_nl = do
329 printf " - initial cluster score: %.8f\n" $ Cluster.compCV ini_nl::IO ()
330 printf " - final cluster score: %.8f\n" $ Cluster.compCV fin_nl
332 -- | Displays the cluster efficiency.
333 printClusterEff :: Cluster.CStats -> IO ()
336 printf " - %s usage efficiency: %5.2f%%\n" s (fn cs * 100))
341 -- | Computes the most likely failure reason.
342 failureReason :: [(FailMode, Int)] -> String
343 failureReason = show . fst . head
345 -- | Sorts the failure reasons.
346 sortReasons :: [(FailMode, Int)] -> [(FailMode, Int)]
347 sortReasons = reverse . sortBy (comparing snd)
349 -- | Aborts the program if we get a bad value.
350 exitIfBad :: Result a -> IO a
352 hPrintf stderr "Failure: %s\n" s >> exitWith (ExitFailure 1)
353 exitIfBad (Ok v) = return v
355 -- | Runs an allocation algorithm and saves cluster state.
356 runAllocation :: ClusterData -- ^ Cluster data
357 -> Maybe Cluster.AllocResult -- ^ Optional stop-allocation
358 -> Result Cluster.AllocResult -- ^ Allocation result
359 -> RSpec -- ^ Requested instance spec
360 -> SpecType -- ^ Allocation type
361 -> Options -- ^ CLI options
362 -> IO (FailStats, Node.List, Int, [(RSpec, Int)])
363 runAllocation cdata stop_allocation actual_result spec mode opts = do
364 (reasons, new_nl, new_il, new_ixes, _) <-
365 case stop_allocation of
366 Just result_noalloc -> return result_noalloc
367 Nothing -> exitIfBad actual_result
369 let name = head . words . specDescription $ mode
370 descr = name ++ " allocation"
371 ldescr = "after " ++ map toLower descr
373 printISpec (optMachineReadable opts) spec mode (optDiskTemplate opts)
375 printAllocationMap (optVerbose opts) descr new_nl new_ixes
377 maybePrintNodes (optShowNodes opts) descr (Cluster.printNodes new_nl)
379 maybeSaveData (optSaveCluster opts) (map toLower name) ldescr
380 (cdata { cdNodes = new_nl, cdInstances = new_il})
382 return (sortReasons reasons, new_nl, length new_ixes, tieredSpecMap new_ixes)
388 (opts, args) <- parseOpts cmd_args "hspace" options
390 unless (null args) $ do
391 hPutStrLn stderr "Error: this program doesn't take any arguments."
392 exitWith $ ExitFailure 1
394 let verbose = optVerbose opts
395 ispec = optISpec opts
396 disk_template = optDiskTemplate opts
397 req_nodes = Instance.requiredNodes disk_template
398 machine_r = optMachineReadable opts
400 (ClusterData gl fixed_nl il ctags) <- loadExternalData opts
401 nl <- setNodeStatus opts fixed_nl
403 let num_instances = Container.size il
404 all_nodes = Container.elems fixed_nl
405 cdata = ClusterData gl nl il ctags
406 csf = commonSuffix fixed_nl il
408 when (not (null csf) && verbose > 1) $
409 hPrintf stderr "Note: Stripping common suffix of '%s' from names\n" csf
411 maybePrintNodes (optShowNodes opts) "Initial cluster" (Cluster.printNodes nl)
414 hPrintf stderr "Initial coefficients: overall %.8f, %s\n"
415 (Cluster.compCV nl) (Cluster.printStats nl)
417 printCluster machine_r (Cluster.totalResources nl) (length all_nodes)
419 let stop_allocation = case Cluster.computeBadItems nl il of
421 _ -> Just ([(FailN1, 1)]::FailStats, nl, il, [], [])
422 alloclimit = if optMaxLength opts == -1
424 else Just (optMaxLength opts)
427 let iofspec spx = Instance.create "new" (rspecMem spx) (rspecDsk spx)
428 (rspecCpu spx) Running [] True (-1) (-1) disk_template
430 allocnodes <- exitIfBad $ Cluster.genAllocNodes gl nl req_nodes True
432 -- Run the tiered allocation, if enabled
434 case optTieredSpec opts of
437 (treason, trl_nl, _, spec_map) <-
438 runAllocation cdata stop_allocation
439 (Cluster.tieredAlloc nl il alloclimit (iofspec tspec)
440 allocnodes [] []) tspec SpecTiered opts
442 printTiered machine_r spec_map (optMcpu opts) nl trl_nl treason
444 -- Run the standard (avg-mode) allocation
446 (sreason, fin_nl, allocs, _) <-
447 runAllocation cdata stop_allocation
448 (Cluster.iterateAlloc nl il alloclimit (iofspec ispec)
449 allocnodes [] []) ispec SpecNormal opts
451 printResults machine_r nl fin_nl num_instances allocs sreason