1 {-| Cluster space sizing
7 Copyright (C) 2009, 2010, 2011, 2012, 2013 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 -- | The \"name\" of a 'SpecType'.
109 specName :: SpecType -> String
110 specName SpecNormal = "Standard"
111 specName SpecTiered = "Tiered"
113 -- | Efficiency generic function.
114 effFn :: (Cluster.CStats -> Integer)
115 -> (Cluster.CStats -> Double)
116 -> Cluster.CStats -> Double
117 effFn fi ft cs = fromIntegral (fi cs) / ft cs
119 -- | Memory efficiency.
120 memEff :: Cluster.CStats -> Double
121 memEff = effFn Cluster.csImem Cluster.csTmem
123 -- | Disk efficiency.
124 dskEff :: Cluster.CStats -> Double
125 dskEff = effFn Cluster.csIdsk Cluster.csTdsk
128 cpuEff :: Cluster.CStats -> Double
129 cpuEff = effFn Cluster.csIcpu (fromIntegral . Cluster.csVcpu)
131 -- | Holds data for converting a 'Cluster.CStats' structure into
132 -- detailed statistics.
133 statsData :: [(String, Cluster.CStats -> String)]
134 statsData = [ ("SCORE", printf "%.8f" . Cluster.csScore)
135 , ("INST_CNT", printf "%d" . Cluster.csNinst)
136 , ("MEM_FREE", printf "%d" . Cluster.csFmem)
137 , ("MEM_AVAIL", printf "%d" . Cluster.csAmem)
139 \cs -> printf "%d" (Cluster.csFmem cs - Cluster.csAmem cs))
140 , ("MEM_INST", printf "%d" . Cluster.csImem)
142 \cs -> printf "%d" (Cluster.csXmem cs + Cluster.csNmem cs))
143 , ("MEM_EFF", printf "%.8f" . memEff)
144 , ("DSK_FREE", printf "%d" . Cluster.csFdsk)
145 , ("DSK_AVAIL", printf "%d". Cluster.csAdsk)
147 \cs -> printf "%d" (Cluster.csFdsk cs - Cluster.csAdsk cs))
148 , ("DSK_INST", printf "%d" . Cluster.csIdsk)
149 , ("DSK_EFF", printf "%.8f" . dskEff)
150 , ("CPU_INST", printf "%d" . Cluster.csIcpu)
151 , ("CPU_EFF", printf "%.8f" . cpuEff)
152 , ("MNODE_MEM_AVAIL", printf "%d" . Cluster.csMmem)
153 , ("MNODE_DSK_AVAIL", printf "%d" . Cluster.csMdsk)
156 -- | List holding 'RSpec' formatting information.
157 specData :: [(String, RSpec -> String)]
158 specData = [ ("MEM", printf "%d" . rspecMem)
159 , ("DSK", printf "%d" . rspecDsk)
160 , ("CPU", printf "%d" . rspecCpu)
163 -- | List holding 'Cluster.CStats' formatting information.
164 clusterData :: [(String, Cluster.CStats -> String)]
165 clusterData = [ ("MEM", printf "%.0f" . Cluster.csTmem)
166 , ("DSK", printf "%.0f" . Cluster.csTdsk)
167 , ("CPU", printf "%.0f" . Cluster.csTcpu)
168 , ("VCPU", printf "%d" . Cluster.csVcpu)
171 -- | Function to print stats for a given phase.
172 printStats :: Phase -> Cluster.CStats -> [(String, String)]
174 map (\(s, fn) -> (printf "%s_%s" kind s, fn cs)) statsData
175 where kind = case ph of
180 -- | Print failure reason and scores
181 printFRScores :: Node.List -> Node.List -> [(FailMode, Int)] -> IO ()
182 printFRScores ini_nl fin_nl sreason = do
183 printf " - most likely failure reason: %s\n" $ failureReason sreason::IO ()
184 printClusterScores ini_nl fin_nl
185 printClusterEff (Cluster.totalResources fin_nl)
187 -- | Print final stats and related metrics.
188 printResults :: Bool -> Node.List -> Node.List -> Int -> Int
189 -> [(FailMode, Int)] -> IO ()
190 printResults True _ fin_nl num_instances allocs sreason = do
191 let fin_stats = Cluster.totalResources fin_nl
192 fin_instances = num_instances + allocs
194 exitWhen (num_instances + allocs /= Cluster.csNinst fin_stats) $
195 printf "internal inconsistency, allocated (%d)\
196 \ != counted (%d)\n" (num_instances + allocs)
197 (Cluster.csNinst fin_stats)
199 main_reason <- exitIfEmpty "Internal error, no failure reasons?!" sreason
201 printKeysHTS $ printStats PFinal fin_stats
202 printKeysHTS [ ("ALLOC_USAGE", printf "%.8f"
203 ((fromIntegral num_instances::Double) /
204 fromIntegral fin_instances))
205 , ("ALLOC_INSTANCES", printf "%d" allocs)
206 , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ main_reason)
208 printKeysHTS $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x),
209 printf "%d" y)) sreason
211 printResults False ini_nl fin_nl _ allocs sreason = do
212 putStrLn "Normal (fixed-size) allocation results:"
213 printf " - %3d instances allocated\n" allocs :: IO ()
214 printFRScores ini_nl fin_nl sreason
216 -- | Prints the final @OK@ marker in machine readable output.
217 printFinalHTS :: Bool -> IO ()
218 printFinalHTS = printFinal htsPrefix
220 {-# ANN tieredSpecMap "HLint: ignore Use alternative" #-}
221 -- | Compute the tiered spec counts from a list of allocated
223 tieredSpecMap :: [Instance.Instance]
225 tieredSpecMap trl_ixes =
226 let fin_trl_ixes = reverse trl_ixes
227 ix_byspec = groupBy ((==) `on` Instance.specOf) fin_trl_ixes
228 -- head is "safe" here, as groupBy returns list of non-empty lists
229 spec_map = map (\ixs -> (Instance.specOf $ head ixs, length ixs))
233 -- | Formats a spec map to strings.
234 formatSpecMap :: [(RSpec, Int)] -> [String]
236 map (\(spec, cnt) -> printf "%d,%d,%d=%d" (rspecMem spec)
237 (rspecDsk spec) (rspecCpu spec) cnt)
239 -- | Formats \"key-metrics\" values.
240 formatRSpec :: String -> AllocInfo -> [(String, String)]
242 [ ("KM_" ++ s ++ "_CPU", show $ allocInfoVCpus r)
243 , ("KM_" ++ s ++ "_NPU", show $ allocInfoNCpus r)
244 , ("KM_" ++ s ++ "_MEM", show $ allocInfoMem r)
245 , ("KM_" ++ s ++ "_DSK", show $ allocInfoDisk r)
248 -- | Shows allocations stats.
249 printAllocationStats :: Node.List -> Node.List -> IO ()
250 printAllocationStats ini_nl fin_nl = do
251 let ini_stats = Cluster.totalResources ini_nl
252 fin_stats = Cluster.totalResources fin_nl
253 (rini, ralo, runa) = Cluster.computeAllocationDelta ini_stats fin_stats
254 printKeysHTS $ formatRSpec "USED" rini
255 printKeysHTS $ formatRSpec "POOL" ralo
256 printKeysHTS $ formatRSpec "UNAV" runa
258 -- | Format a list of key\/values as a shell fragment.
259 printKeysHTS :: [(String, String)] -> IO ()
260 printKeysHTS = printKeys htsPrefix
262 -- | Converts instance data to a list of strings.
263 printInstance :: Node.List -> Instance.Instance -> [String]
264 printInstance nl i = [ Instance.name i
265 , Container.nameOf nl $ Instance.pNode i
266 , let sdx = Instance.sNode i
267 in if sdx == Node.noSecondary then ""
268 else Container.nameOf nl sdx
269 , show (Instance.mem i)
270 , show (Instance.dsk i)
271 , show (Instance.vcpus i)
274 -- | Optionally print the allocation map.
275 printAllocationMap :: Int -> String
276 -> Node.List -> [Instance.Instance] -> IO ()
277 printAllocationMap verbose msg nl ixes =
278 when (verbose > 1) $ do
279 hPutStrLn stderr (msg ++ " map")
280 hPutStr stderr . unlines . map ((:) ' ' . unwords) $
281 formatTable (map (printInstance nl) (reverse ixes))
282 -- This is the numberic-or-not field
283 -- specification; the first three fields are
284 -- strings, whereas the rest are numeric
285 [False, False, False, True, True, True]
287 -- | Formats nicely a list of resources.
288 formatResources :: a -> [(String, a->String)] -> String
289 formatResources res =
290 intercalate ", " . map (\(a, fn) -> a ++ " " ++ fn res)
292 -- | Print the cluster resources.
293 printCluster :: Bool -> Cluster.CStats -> Int -> IO ()
294 printCluster True ini_stats node_count = do
295 printKeysHTS $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats)) clusterData
296 printKeysHTS [("CLUSTER_NODES", printf "%d" node_count)]
297 printKeysHTS $ printStats PInitial ini_stats
299 printCluster False ini_stats node_count = do
300 printf "The cluster has %d nodes and the following resources:\n %s.\n"
301 node_count (formatResources ini_stats clusterData)::IO ()
302 printf "There are %s initial instances on the cluster.\n"
303 (if inst_count > 0 then show inst_count else "no" )
304 where inst_count = Cluster.csNinst ini_stats
306 -- | Prints the normal instance spec.
307 printISpec :: Bool -> RSpec -> SpecType -> DiskTemplate -> IO ()
308 printISpec True ispec spec disk_template = do
309 printKeysHTS $ map (\(a, fn) -> (prefix ++ "_" ++ a, fn ispec)) specData
310 printKeysHTS [ (prefix ++ "_RQN", printf "%d" req_nodes) ]
311 printKeysHTS [ (prefix ++ "_DISK_TEMPLATE",
312 diskTemplateToRaw disk_template) ]
313 where req_nodes = Instance.requiredNodes disk_template
314 prefix = specPrefix spec
316 printISpec False ispec spec disk_template =
317 printf "%s instance spec is:\n %s, using disk\
319 (specDescription spec)
320 (formatResources ispec specData) (diskTemplateToRaw disk_template)
322 -- | Prints the tiered results.
323 printTiered :: Bool -> [(RSpec, Int)]
324 -> Node.List -> Node.List -> [(FailMode, Int)] -> IO ()
325 printTiered True spec_map nl trl_nl _ = do
326 printKeysHTS $ printStats PTiered (Cluster.totalResources trl_nl)
327 printKeysHTS [("TSPEC", unwords (formatSpecMap spec_map))]
328 printAllocationStats nl trl_nl
330 printTiered False spec_map ini_nl fin_nl sreason = do
331 _ <- printf "Tiered allocation results:\n"
333 then putStrLn " - no instances allocated"
334 else mapM_ (\(ispec, cnt) ->
335 printf " - %3d instances of spec %s\n" cnt
336 (formatResources ispec specData)) spec_map
337 printFRScores ini_nl fin_nl sreason
339 -- | Displays the initial/final cluster scores.
340 printClusterScores :: Node.List -> Node.List -> IO ()
341 printClusterScores ini_nl fin_nl = do
342 printf " - initial cluster score: %.8f\n" $ Cluster.compCV ini_nl::IO ()
343 printf " - final cluster score: %.8f\n" $ Cluster.compCV fin_nl
345 -- | Displays the cluster efficiency.
346 printClusterEff :: Cluster.CStats -> IO ()
349 printf " - %s usage efficiency: %5.2f%%\n" s (fn cs * 100))
354 -- | Computes the most likely failure reason.
355 failureReason :: [(FailMode, Int)] -> String
356 failureReason = show . fst . head
358 -- | Sorts the failure reasons.
359 sortReasons :: [(FailMode, Int)] -> [(FailMode, Int)]
360 sortReasons = reverse . sortBy (comparing snd)
362 -- | Runs an allocation algorithm and saves cluster state.
363 runAllocation :: ClusterData -- ^ Cluster data
364 -> Maybe Cluster.AllocResult -- ^ Optional stop-allocation
365 -> Result Cluster.AllocResult -- ^ Allocation result
366 -> RSpec -- ^ Requested instance spec
367 -> DiskTemplate -- ^ Requested disk template
368 -> SpecType -- ^ Allocation type
369 -> Options -- ^ CLI options
370 -> IO (FailStats, Node.List, Int, [(RSpec, Int)])
371 runAllocation cdata stop_allocation actual_result spec dt mode opts = do
372 (reasons, new_nl, new_il, new_ixes, _) <-
373 case stop_allocation of
374 Just result_noalloc -> return result_noalloc
375 Nothing -> exitIfBad "failure during allocation" actual_result
377 let name = specName mode
378 descr = name ++ " allocation"
379 ldescr = "after " ++ map toLower descr
381 printISpec (optMachineReadable opts) spec mode dt
383 printAllocationMap (optVerbose opts) descr new_nl new_ixes
385 maybePrintNodes (optShowNodes opts) descr (Cluster.printNodes new_nl)
387 maybeSaveData (optSaveCluster opts) (map toLower name) ldescr
388 (cdata { cdNodes = new_nl, cdInstances = new_il})
390 return (sortReasons reasons, new_nl, length new_ixes, tieredSpecMap new_ixes)
392 -- | Create an instance from a given spec.
393 instFromSpec :: RSpec -> DiskTemplate -> Int -> Instance.Instance
395 Instance.create "new" (rspecMem spx) (rspecDsk spx)
396 (rspecCpu spx) Running [] True (-1) (-1)
399 main :: Options -> [String] -> IO ()
401 exitUnless (null args) "This program doesn't take any arguments."
403 let verbose = optVerbose opts
404 machine_r = optMachineReadable opts
406 orig_cdata@(ClusterData gl fixed_nl il _ ipol) <- loadExternalData opts
407 nl <- setNodeStatus opts fixed_nl
409 cluster_disk_template <-
410 case iPolicyDiskTemplates ipol of
411 first_templ:_ -> return first_templ
412 _ -> exitErr "null list of disk templates received from cluster"
414 let num_instances = Container.size il
415 all_nodes = Container.elems fixed_nl
416 cdata = orig_cdata { cdNodes = fixed_nl }
417 disk_template = fromMaybe cluster_disk_template (optDiskTemplate opts)
418 req_nodes = Instance.requiredNodes disk_template
419 csf = commonSuffix fixed_nl il
420 su = fromMaybe (iSpecSpindleUse $ iPolicyStdSpec ipol)
423 when (not (null csf) && verbose > 1) $
424 hPrintf stderr "Note: Stripping common suffix of '%s' from names\n" csf
426 maybePrintNodes (optShowNodes opts) "Initial cluster" (Cluster.printNodes nl)
429 hPrintf stderr "Initial coefficients: overall %.8f\n%s"
430 (Cluster.compCV nl) (Cluster.printStats " " nl)
432 printCluster machine_r (Cluster.totalResources nl) (length all_nodes)
434 let stop_allocation = case Cluster.computeBadItems nl il of
436 _ -> Just ([(FailN1, 1)]::FailStats, nl, il, [], [])
437 alloclimit = if optMaxLength opts == -1
439 else Just (optMaxLength opts)
441 allocnodes <- exitIfBad "failure during allocation" $
442 Cluster.genAllocNodes gl nl req_nodes True
444 -- Run the tiered allocation
446 let tspec = fromMaybe (rspecFromISpec (iPolicyMaxSpec ipol))
449 (treason, trl_nl, _, spec_map) <-
450 runAllocation cdata stop_allocation
451 (Cluster.tieredAlloc nl il alloclimit
452 (instFromSpec tspec disk_template su) allocnodes [] [])
453 tspec disk_template SpecTiered opts
455 printTiered machine_r spec_map nl trl_nl treason
457 -- Run the standard (avg-mode) allocation
459 let ispec = fromMaybe (rspecFromISpec (iPolicyStdSpec ipol))
462 (sreason, fin_nl, allocs, _) <-
463 runAllocation cdata stop_allocation
464 (Cluster.iterateAlloc nl il alloclimit
465 (instFromSpec ispec disk_template su) allocnodes [] [])
466 ispec disk_template SpecNormal opts
468 printResults machine_r nl fin_nl num_instances allocs sreason
470 -- Print final result
472 printFinalHTS machine_r