1 {-| Cluster space sizing
7 Copyright (C) 2009, 2010, 2011, 2012 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
33 import Data.Char (toUpper, toLower)
34 import Data.Function (on)
36 import Data.Maybe (fromMaybe)
37 import Data.Ord (comparing)
40 import Text.Printf (printf, hPrintf)
42 import qualified Ganeti.HTools.Container as Container
43 import qualified Ganeti.HTools.Cluster as Cluster
44 import qualified Ganeti.HTools.Node as Node
45 import qualified Ganeti.HTools.Instance as Instance
47 import Ganeti.BasicTypes
49 import Ganeti.HTools.Types
50 import Ganeti.HTools.CLI
51 import Ganeti.HTools.ExtLoader
52 import Ganeti.HTools.Loader
55 -- | Options list and functions.
56 options :: IO [OptType]
80 -- | The list of arguments supported by the program.
81 arguments :: [ArgCompletion]
84 -- | The allocation phase we're in (initial, after tiered allocs, or
85 -- after regular allocation).
90 -- | The kind of instance spec we print.
91 data SpecType = SpecNormal
94 -- | Prefix for machine readable names
98 -- | What we prefix a spec with.
99 specPrefix :: SpecType -> String
100 specPrefix SpecNormal = "SPEC"
101 specPrefix SpecTiered = "TSPEC_INI"
103 -- | The description of a spec.
104 specDescription :: SpecType -> String
105 specDescription SpecNormal = "Standard (fixed-size)"
106 specDescription SpecTiered = "Tiered (initial size)"
108 -- | Efficiency generic function.
109 effFn :: (Cluster.CStats -> Integer)
110 -> (Cluster.CStats -> Double)
111 -> Cluster.CStats -> Double
112 effFn fi ft cs = fromIntegral (fi cs) / ft cs
114 -- | Memory efficiency.
115 memEff :: Cluster.CStats -> Double
116 memEff = effFn Cluster.csImem Cluster.csTmem
118 -- | Disk efficiency.
119 dskEff :: Cluster.CStats -> Double
120 dskEff = effFn Cluster.csIdsk Cluster.csTdsk
123 cpuEff :: Cluster.CStats -> Double
124 cpuEff = effFn Cluster.csIcpu (fromIntegral . Cluster.csVcpu)
126 -- | Holds data for converting a 'Cluster.CStats' structure into
127 -- detailed statistics.
128 statsData :: [(String, Cluster.CStats -> String)]
129 statsData = [ ("SCORE", printf "%.8f" . Cluster.csScore)
130 , ("INST_CNT", printf "%d" . Cluster.csNinst)
131 , ("MEM_FREE", printf "%d" . Cluster.csFmem)
132 , ("MEM_AVAIL", printf "%d" . Cluster.csAmem)
134 \cs -> printf "%d" (Cluster.csFmem cs - Cluster.csAmem cs))
135 , ("MEM_INST", printf "%d" . Cluster.csImem)
137 \cs -> printf "%d" (Cluster.csXmem cs + Cluster.csNmem cs))
138 , ("MEM_EFF", printf "%.8f" . memEff)
139 , ("DSK_FREE", printf "%d" . Cluster.csFdsk)
140 , ("DSK_AVAIL", printf "%d". Cluster.csAdsk)
142 \cs -> printf "%d" (Cluster.csFdsk cs - Cluster.csAdsk cs))
143 , ("DSK_INST", printf "%d" . Cluster.csIdsk)
144 , ("DSK_EFF", printf "%.8f" . dskEff)
145 , ("CPU_INST", printf "%d" . Cluster.csIcpu)
146 , ("CPU_EFF", printf "%.8f" . cpuEff)
147 , ("MNODE_MEM_AVAIL", printf "%d" . Cluster.csMmem)
148 , ("MNODE_DSK_AVAIL", printf "%d" . Cluster.csMdsk)
151 -- | List holding 'RSpec' formatting information.
152 specData :: [(String, RSpec -> String)]
153 specData = [ ("MEM", printf "%d" . rspecMem)
154 , ("DSK", printf "%d" . rspecDsk)
155 , ("CPU", printf "%d" . rspecCpu)
158 -- | List holding 'Cluster.CStats' formatting information.
159 clusterData :: [(String, Cluster.CStats -> String)]
160 clusterData = [ ("MEM", printf "%.0f" . Cluster.csTmem)
161 , ("DSK", printf "%.0f" . Cluster.csTdsk)
162 , ("CPU", printf "%.0f" . Cluster.csTcpu)
163 , ("VCPU", printf "%d" . Cluster.csVcpu)
166 -- | Function to print stats for a given phase.
167 printStats :: Phase -> Cluster.CStats -> [(String, String)]
169 map (\(s, fn) -> (printf "%s_%s" kind s, fn cs)) statsData
170 where kind = case ph of
175 -- | Print failure reason and scores
176 printFRScores :: Node.List -> Node.List -> [(FailMode, Int)] -> IO ()
177 printFRScores ini_nl fin_nl sreason = do
178 printf " - most likely failure reason: %s\n" $ failureReason sreason::IO ()
179 printClusterScores ini_nl fin_nl
180 printClusterEff (Cluster.totalResources fin_nl)
182 -- | Print final stats and related metrics.
183 printResults :: Bool -> Node.List -> Node.List -> Int -> Int
184 -> [(FailMode, Int)] -> IO ()
185 printResults True _ fin_nl num_instances allocs sreason = do
186 let fin_stats = Cluster.totalResources fin_nl
187 fin_instances = num_instances + allocs
189 exitWhen (num_instances + allocs /= Cluster.csNinst fin_stats) $
190 printf "internal inconsistency, allocated (%d)\
191 \ != counted (%d)\n" (num_instances + allocs)
192 (Cluster.csNinst fin_stats)
194 printKeysHTS $ printStats PFinal fin_stats
195 printKeysHTS [ ("ALLOC_USAGE", printf "%.8f"
196 ((fromIntegral num_instances::Double) /
197 fromIntegral fin_instances))
198 , ("ALLOC_INSTANCES", printf "%d" allocs)
199 , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ head sreason)
201 printKeysHTS $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x),
202 printf "%d" y)) sreason
204 printResults False ini_nl fin_nl _ allocs sreason = do
205 putStrLn "Normal (fixed-size) allocation results:"
206 printf " - %3d instances allocated\n" allocs :: IO ()
207 printFRScores ini_nl fin_nl sreason
209 -- | Prints the final @OK@ marker in machine readable output.
210 printFinalHTS :: Bool -> IO ()
211 printFinalHTS = printFinal htsPrefix
213 -- | Compute the tiered spec counts from a list of allocated
215 tieredSpecMap :: [Instance.Instance]
217 tieredSpecMap trl_ixes =
218 let fin_trl_ixes = reverse trl_ixes
219 ix_byspec = groupBy ((==) `on` Instance.specOf) fin_trl_ixes
220 spec_map = map (\ixs -> (Instance.specOf $ head ixs, length ixs))
224 -- | Formats a spec map to strings.
225 formatSpecMap :: [(RSpec, Int)] -> [String]
227 map (\(spec, cnt) -> printf "%d,%d,%d=%d" (rspecMem spec)
228 (rspecDsk spec) (rspecCpu spec) cnt)
230 -- | Formats \"key-metrics\" values.
231 formatRSpec :: String -> AllocInfo -> [(String, String)]
233 [ ("KM_" ++ s ++ "_CPU", show $ allocInfoVCpus r)
234 , ("KM_" ++ s ++ "_NPU", show $ allocInfoNCpus r)
235 , ("KM_" ++ s ++ "_MEM", show $ allocInfoMem r)
236 , ("KM_" ++ s ++ "_DSK", show $ allocInfoDisk r)
239 -- | Shows allocations stats.
240 printAllocationStats :: Node.List -> Node.List -> IO ()
241 printAllocationStats ini_nl fin_nl = do
242 let ini_stats = Cluster.totalResources ini_nl
243 fin_stats = Cluster.totalResources fin_nl
244 (rini, ralo, runa) = Cluster.computeAllocationDelta ini_stats fin_stats
245 printKeysHTS $ formatRSpec "USED" rini
246 printKeysHTS $ formatRSpec "POOL" ralo
247 printKeysHTS $ formatRSpec "UNAV" runa
249 -- | Format a list of key\/values as a shell fragment.
250 printKeysHTS :: [(String, String)] -> IO ()
251 printKeysHTS = printKeys htsPrefix
253 -- | Converts instance data to a list of strings.
254 printInstance :: Node.List -> Instance.Instance -> [String]
255 printInstance nl i = [ Instance.name i
256 , Container.nameOf nl $ Instance.pNode i
257 , let sdx = Instance.sNode i
258 in if sdx == Node.noSecondary then ""
259 else Container.nameOf nl sdx
260 , show (Instance.mem i)
261 , show (Instance.dsk i)
262 , show (Instance.vcpus i)
265 -- | Optionally print the allocation map.
266 printAllocationMap :: Int -> String
267 -> Node.List -> [Instance.Instance] -> IO ()
268 printAllocationMap verbose msg nl ixes =
269 when (verbose > 1) $ do
270 hPutStrLn stderr (msg ++ " map")
271 hPutStr stderr . unlines . map ((:) ' ' . unwords) $
272 formatTable (map (printInstance nl) (reverse ixes))
273 -- This is the numberic-or-not field
274 -- specification; the first three fields are
275 -- strings, whereas the rest are numeric
276 [False, False, False, True, True, True]
278 -- | Formats nicely a list of resources.
279 formatResources :: a -> [(String, a->String)] -> String
280 formatResources res =
281 intercalate ", " . map (\(a, fn) -> a ++ " " ++ fn res)
283 -- | Print the cluster resources.
284 printCluster :: Bool -> Cluster.CStats -> Int -> IO ()
285 printCluster True ini_stats node_count = do
286 printKeysHTS $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats)) clusterData
287 printKeysHTS [("CLUSTER_NODES", printf "%d" node_count)]
288 printKeysHTS $ printStats PInitial ini_stats
290 printCluster False ini_stats node_count = do
291 printf "The cluster has %d nodes and the following resources:\n %s.\n"
292 node_count (formatResources ini_stats clusterData)::IO ()
293 printf "There are %s initial instances on the cluster.\n"
294 (if inst_count > 0 then show inst_count else "no" )
295 where inst_count = Cluster.csNinst ini_stats
297 -- | Prints the normal instance spec.
298 printISpec :: Bool -> RSpec -> SpecType -> DiskTemplate -> IO ()
299 printISpec True ispec spec disk_template = do
300 printKeysHTS $ map (\(a, fn) -> (prefix ++ "_" ++ a, fn ispec)) specData
301 printKeysHTS [ (prefix ++ "_RQN", printf "%d" req_nodes) ]
302 printKeysHTS [ (prefix ++ "_DISK_TEMPLATE",
303 diskTemplateToRaw disk_template) ]
304 where req_nodes = Instance.requiredNodes disk_template
305 prefix = specPrefix spec
307 printISpec False ispec spec disk_template =
308 printf "%s instance spec is:\n %s, using disk\
310 (specDescription spec)
311 (formatResources ispec specData) (diskTemplateToRaw disk_template)
313 -- | Prints the tiered results.
314 printTiered :: Bool -> [(RSpec, Int)]
315 -> Node.List -> Node.List -> [(FailMode, Int)] -> IO ()
316 printTiered True spec_map nl trl_nl _ = do
317 printKeysHTS $ printStats PTiered (Cluster.totalResources trl_nl)
318 printKeysHTS [("TSPEC", unwords (formatSpecMap spec_map))]
319 printAllocationStats nl trl_nl
321 printTiered False spec_map ini_nl fin_nl sreason = do
322 _ <- printf "Tiered allocation results:\n"
324 then putStrLn " - no instances allocated"
325 else mapM_ (\(ispec, cnt) ->
326 printf " - %3d instances of spec %s\n" cnt
327 (formatResources ispec specData)) spec_map
328 printFRScores ini_nl fin_nl sreason
330 -- | Displays the initial/final cluster scores.
331 printClusterScores :: Node.List -> Node.List -> IO ()
332 printClusterScores ini_nl fin_nl = do
333 printf " - initial cluster score: %.8f\n" $ Cluster.compCV ini_nl::IO ()
334 printf " - final cluster score: %.8f\n" $ Cluster.compCV fin_nl
336 -- | Displays the cluster efficiency.
337 printClusterEff :: Cluster.CStats -> IO ()
340 printf " - %s usage efficiency: %5.2f%%\n" s (fn cs * 100))
345 -- | Computes the most likely failure reason.
346 failureReason :: [(FailMode, Int)] -> String
347 failureReason = show . fst . head
349 -- | Sorts the failure reasons.
350 sortReasons :: [(FailMode, Int)] -> [(FailMode, Int)]
351 sortReasons = reverse . sortBy (comparing snd)
353 -- | Runs an allocation algorithm and saves cluster state.
354 runAllocation :: ClusterData -- ^ Cluster data
355 -> Maybe Cluster.AllocResult -- ^ Optional stop-allocation
356 -> Result Cluster.AllocResult -- ^ Allocation result
357 -> RSpec -- ^ Requested instance spec
358 -> DiskTemplate -- ^ Requested disk template
359 -> SpecType -- ^ Allocation type
360 -> Options -- ^ CLI options
361 -> IO (FailStats, Node.List, Int, [(RSpec, Int)])
362 runAllocation cdata stop_allocation actual_result spec dt mode opts = do
363 (reasons, new_nl, new_il, new_ixes, _) <-
364 case stop_allocation of
365 Just result_noalloc -> return result_noalloc
366 Nothing -> exitIfBad "failure during allocation" actual_result
368 let name = head . words . specDescription $ mode
369 descr = name ++ " allocation"
370 ldescr = "after " ++ map toLower descr
372 printISpec (optMachineReadable opts) spec mode dt
374 printAllocationMap (optVerbose opts) descr new_nl new_ixes
376 maybePrintNodes (optShowNodes opts) descr (Cluster.printNodes new_nl)
378 maybeSaveData (optSaveCluster opts) (map toLower name) ldescr
379 (cdata { cdNodes = new_nl, cdInstances = new_il})
381 return (sortReasons reasons, new_nl, length new_ixes, tieredSpecMap new_ixes)
383 -- | Create an instance from a given spec.
384 instFromSpec :: RSpec -> DiskTemplate -> Int -> Instance.Instance
386 Instance.create "new" (rspecMem spx) (rspecDsk spx)
387 (rspecCpu spx) Running [] True (-1) (-1)
390 main :: Options -> [String] -> IO ()
392 exitUnless (null args) "This program doesn't take any arguments."
394 let verbose = optVerbose opts
395 machine_r = optMachineReadable opts
397 orig_cdata@(ClusterData gl fixed_nl il _ ipol) <- loadExternalData opts
398 nl <- setNodeStatus opts fixed_nl
400 cluster_disk_template <-
401 case iPolicyDiskTemplates ipol of
402 first_templ:_ -> return first_templ
403 _ -> exitErr "null list of disk templates received from cluster"
405 let num_instances = Container.size il
406 all_nodes = Container.elems fixed_nl
407 cdata = orig_cdata { cdNodes = fixed_nl }
408 disk_template = fromMaybe cluster_disk_template (optDiskTemplate opts)
409 req_nodes = Instance.requiredNodes disk_template
410 csf = commonSuffix fixed_nl il
411 su = fromMaybe (iSpecSpindleUse $ iPolicyStdSpec ipol)
414 when (not (null csf) && verbose > 1) $
415 hPrintf stderr "Note: Stripping common suffix of '%s' from names\n" csf
417 maybePrintNodes (optShowNodes opts) "Initial cluster" (Cluster.printNodes nl)
420 hPrintf stderr "Initial coefficients: overall %.8f\n%s"
421 (Cluster.compCV nl) (Cluster.printStats " " nl)
423 printCluster machine_r (Cluster.totalResources nl) (length all_nodes)
425 let stop_allocation = case Cluster.computeBadItems nl il of
427 _ -> Just ([(FailN1, 1)]::FailStats, nl, il, [], [])
428 alloclimit = if optMaxLength opts == -1
430 else Just (optMaxLength opts)
432 allocnodes <- exitIfBad "failure during allocation" $
433 Cluster.genAllocNodes gl nl req_nodes True
435 -- Run the tiered allocation
437 let tspec = fromMaybe (rspecFromISpec (iPolicyMaxSpec ipol))
440 (treason, trl_nl, _, spec_map) <-
441 runAllocation cdata stop_allocation
442 (Cluster.tieredAlloc nl il alloclimit
443 (instFromSpec tspec disk_template su) allocnodes [] [])
444 tspec disk_template SpecTiered opts
446 printTiered machine_r spec_map nl trl_nl treason
448 -- Run the standard (avg-mode) allocation
450 let ispec = fromMaybe (rspecFromISpec (iPolicyStdSpec ipol))
453 (sreason, fin_nl, allocs, _) <-
454 runAllocation cdata stop_allocation
455 (Cluster.iterateAlloc nl il alloclimit
456 (instFromSpec ispec disk_template su) allocnodes [] [])
457 ispec disk_template SpecNormal opts
459 printResults machine_r nl fin_nl num_instances allocs sreason
461 -- Print final result
463 printFinalHTS machine_r