1 {-| Implementation of cluster-wide logic.
3 This module holds all pure cluster-logic; I\/O related functionality
4 goes into the "Main" module for the individual binaries.
10 Copyright (C) 2009 Google Inc.
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful, but
18 WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
29 module Ganeti.HTools.Cluster
38 -- * Generic functions
40 -- * First phase functions
42 -- * Second phase functions
49 -- * Balacing functions
54 -- * IAllocator functions
62 import Data.Maybe (isNothing, fromJust)
63 import Text.Printf (printf)
67 import qualified Ganeti.HTools.Container as Container
68 import qualified Ganeti.HTools.Instance as Instance
69 import qualified Ganeti.HTools.Node as Node
70 import Ganeti.HTools.Types
71 import Ganeti.HTools.Utils
75 -- | A separate name for the cluster score type.
78 -- | The description of an instance placement.
79 type Placement = (Idx, Ndx, Ndx, Score)
81 -- | A cluster solution described as the solution delta and the list
83 data Solution = Solution Int [Placement]
84 deriving (Eq, Ord, Show)
87 data Removal = Removal Node.List [Instance.Instance]
89 -- | An instance move definition
90 data IMove = Failover -- ^ Failover the instance (f)
91 | ReplacePrimary Ndx -- ^ Replace primary (f, r:np, f)
92 | ReplaceSecondary Ndx -- ^ Replace secondary (r:ns)
93 | ReplaceAndFailover Ndx -- ^ Replace secondary, failover (r:np, f)
94 | FailoverAndReplace Ndx -- ^ Failover, replace secondary (f, r:ns)
97 -- | The complete state for the balancing solution
98 data Table = Table Node.List Instance.List Score [Placement]
101 -- * Utility functions
103 -- | Returns the delta of a solution or -1 for Nothing.
104 solutionDelta :: Maybe Solution -> Int
105 solutionDelta sol = case sol of
106 Just (Solution d _) -> d
109 -- | Cap the removal list if needed.
110 capRemovals :: [a] -> Int -> [a]
111 capRemovals removals max_removals =
112 if max_removals > 0 then
113 take max_removals removals
117 -- | Check if the given node list fails the N+1 check.
118 verifyN1Check :: [Node.Node] -> Bool
119 verifyN1Check nl = any Node.failN1 nl
121 -- | Verifies the N+1 status and return the affected nodes.
122 verifyN1 :: [Node.Node] -> [Node.Node]
123 verifyN1 nl = filter Node.failN1 nl
125 {-| Computes the pair of bad nodes and instances.
127 The bad node list is computed via a simple 'verifyN1' check, and the
128 bad instance list is the list of primary and secondary instances of
132 computeBadItems :: Node.List -> Instance.List ->
133 ([Node.Node], [Instance.Instance])
134 computeBadItems nl il =
135 let bad_nodes = verifyN1 $ getOnline nl
136 bad_instances = map (\idx -> Container.find idx il) $
137 sort $ nub $ concat $
138 map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes
140 (bad_nodes, bad_instances)
142 -- | Compute the total free disk and memory in the cluster.
143 totalResources :: Node.List -> (Int, Int)
146 (\ (mem, dsk) node -> (mem + (Node.f_mem node),
147 dsk + (Node.f_dsk node)))
148 (0, 0) (Container.elems nl)
150 -- | Compute the mem and disk covariance.
151 compDetailedCV :: Node.List -> (Double, Double, Double, Double, Double)
154 all_nodes = Container.elems nl
155 (offline, nodes) = partition Node.offline all_nodes
156 mem_l = map Node.p_mem nodes
157 dsk_l = map Node.p_dsk nodes
158 mem_cv = varianceCoeff mem_l
159 dsk_cv = varianceCoeff dsk_l
160 n1_l = length $ filter Node.failN1 nodes
161 n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes)
162 res_l = map Node.p_rem nodes
163 res_cv = varianceCoeff res_l
164 offline_inst = sum . map (\n -> (length . Node.plist $ n) +
165 (length . Node.slist $ n)) $ offline
166 online_inst = sum . map (\n -> (length . Node.plist $ n) +
167 (length . Node.slist $ n)) $ nodes
168 off_score = (fromIntegral offline_inst) /
169 (fromIntegral $ online_inst + offline_inst)
170 in (mem_cv, dsk_cv, n1_score, res_cv, off_score)
172 -- | Compute the /total/ variance.
173 compCV :: Node.List -> Double
175 let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
176 in mem_cv + dsk_cv + n1_score + res_cv + off_score
178 -- | Compute online nodes from a Node.List
179 getOnline :: Node.List -> [Node.Node]
180 getOnline = filter (not . Node.offline) . Container.elems
184 -- | Add an instance and return the new node and instance maps.
185 addInstance :: Node.List -> Instance.Instance ->
186 Node.Node -> Node.Node -> Maybe Node.List
187 addInstance nl idata pri sec =
188 let pdx = Node.idx pri
191 pnode <- Node.addPri pri idata
192 snode <- Node.addSec sec idata pdx
193 new_nl <- return $ Container.addTwo sdx snode
197 -- | Remove an instance and return the new node and instance maps.
198 removeInstance :: Node.List -> Instance.Instance -> Node.List
199 removeInstance nl idata =
200 let pnode = Instance.pnode idata
201 snode = Instance.snode idata
202 pn = Container.find pnode nl
203 sn = Container.find snode nl
204 new_nl = Container.addTwo
205 pnode (Node.removePri pn idata)
206 snode (Node.removeSec sn idata) nl in
209 -- | Remove an instance and return the new node map.
210 removeInstances :: Node.List -> [Instance.Instance] -> Node.List
211 removeInstances = foldl' removeInstance
214 {-| Compute a new version of a cluster given a solution.
216 This is not used for computing the solutions, but for applying a
217 (known-good) solution to the original cluster for final display.
219 It first removes the relocated instances after which it places them on
223 applySolution :: Node.List -> Instance.List -> [Placement] -> Node.List
224 applySolution nl il sol =
225 let odxes = map (\ (a, b, c, _) -> (Container.find a il,
226 Node.idx (Container.find b nl),
227 Node.idx (Container.find c nl))
229 idxes = (\ (x, _, _) -> x) (unzip3 odxes)
230 nc = removeInstances nl idxes
232 foldl' (\ nz (a, b, c) ->
233 let new_p = Container.find b nz
234 new_s = Container.find c nz in
235 fromJust (addInstance nz a new_p new_s)
239 -- ** First phase functions
241 {-| Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
245 genParts :: [a] -> Int -> [(a, [a])]
250 if length l < count then
253 (x, xs) : (genParts xs count)
255 -- | Generates combinations of count items from the names list.
256 genNames :: Int -> [b] -> [[b]]
257 genNames count1 names1 =
258 let aux_fn count names current =
263 (\ (x, xs) -> aux_fn (count - 1) xs (x:current))
264 (genParts names count)
266 aux_fn count1 names1 []
268 {-| Checks if removal of instances results in N+1 pass.
270 Note: the check removal cannot optimize by scanning only the affected
271 nodes, since the cluster is known to be not healthy; only the check
272 placement can make this shortcut.
275 checkRemoval :: Node.List -> [Instance.Instance] -> Maybe Removal
276 checkRemoval nl victims =
277 let nx = removeInstances nl victims
278 failN1 = verifyN1Check (Container.elems nx)
283 Just $ Removal nx victims
286 -- | Computes the removals list for a given depth.
287 computeRemovals :: Node.List
288 -> [Instance.Instance]
291 computeRemovals nl bad_instances depth =
292 map (checkRemoval nl) $ genNames depth bad_instances
294 -- ** Second phase functions
296 -- | Single-node relocation cost.
297 nodeDelta :: Ndx -> Ndx -> Ndx -> Int
299 if i == p || i == s then
304 -- | Compute best solution.
306 -- This function compares two solutions, choosing the minimum valid
308 compareSolutions :: Maybe Solution -> Maybe Solution -> Maybe Solution
309 compareSolutions a b = case (a, b) of
314 -- | Check if a given delta is worse then an existing solution.
315 tooHighDelta :: Maybe Solution -> Int -> Int -> Bool
316 tooHighDelta sol new_delta max_delta =
317 if new_delta > max_delta && max_delta >=0 then
322 Just (Solution old_delta _) -> old_delta <= new_delta
324 {-| Check if placement of instances still keeps the cluster N+1 compliant.
326 This is the workhorse of the allocation algorithm: given the
327 current node and instance maps, the list of instances to be
328 placed, and the current solution, this will return all possible
329 solution by recursing until all target instances are placed.
332 checkPlacement :: Node.List -- ^ The current node list
333 -> [Instance.Instance] -- ^ List of instances still to place
334 -> [Placement] -- ^ Partial solution until now
335 -> Int -- ^ The delta of the partial solution
336 -> Maybe Solution -- ^ The previous solution
337 -> Int -- ^ Abort if the we go above this delta
338 -> Maybe Solution -- ^ The new solution
339 checkPlacement nl victims current current_delta prev_sol max_delta =
340 let target = head victims
341 opdx = Instance.pnode target
342 osdx = Instance.snode target
344 have_tail = (length vtail) > 0
345 nodes = Container.elems nl
346 iidx = Instance.idx target
351 pri_idx = Node.idx pri
352 upri_delta = current_delta + nodeDelta pri_idx opdx osdx
353 new_pri = Node.addPri pri target
354 fail_delta1 = tooHighDelta accu_p upri_delta max_delta
356 if fail_delta1 || isNothing(new_pri) then accu_p
357 else let pri_nl = Container.add pri_idx (fromJust new_pri) nl in
361 sec_idx = Node.idx sec
362 upd_delta = upri_delta +
363 nodeDelta sec_idx opdx osdx
364 fail_delta2 = tooHighDelta accu upd_delta max_delta
365 new_sec = Node.addSec sec target pri_idx
367 if sec_idx == pri_idx || fail_delta2 ||
368 isNothing new_sec then accu
370 nx = Container.add sec_idx (fromJust new_sec) pri_nl
372 plc = (iidx, pri_idx, sec_idx, upd_cv)
376 checkPlacement nx vtail c2 upd_delta
379 Just (Solution upd_delta c2)
380 in compareSolutions accu result
384 {-| Auxiliary function for solution computation.
386 We write this in an explicit recursive fashion in order to control
387 early-abort in case we have met the min delta. We can't use foldr
388 instead of explicit recursion since we need the accumulator for the
392 advanceSolution :: [Maybe Removal] -- ^ The removal to process
393 -> Int -- ^ Minimum delta parameter
394 -> Int -- ^ Maximum delta parameter
395 -> Maybe Solution -- ^ Current best solution
396 -> Maybe Solution -- ^ New best solution
397 advanceSolution [] _ _ sol = sol
398 advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol
399 advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol =
400 let new_sol = checkPlacement nx removed [] 0 prev_sol max_d
401 new_delta = solutionDelta $! new_sol
403 if new_delta >= 0 && new_delta <= min_d then
406 advanceSolution xs min_d max_d new_sol
408 -- | Computes the placement solution.
409 solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals
410 -> Int -- ^ Minimum delta parameter
411 -> Int -- ^ Maximum delta parameter
412 -> Maybe Solution -- ^ The best solution found
413 solutionFromRemovals removals min_delta max_delta =
414 advanceSolution removals min_delta max_delta Nothing
416 {-| Computes the solution at the given depth.
418 This is a wrapper over both computeRemovals and
419 solutionFromRemovals. In case we have no solution, we return Nothing.
422 computeSolution :: Node.List -- ^ The original node data
423 -> [Instance.Instance] -- ^ The list of /bad/ instances
424 -> Int -- ^ The /depth/ of removals
425 -> Int -- ^ Maximum number of removals to process
426 -> Int -- ^ Minimum delta parameter
427 -> Int -- ^ Maximum delta parameter
428 -> Maybe Solution -- ^ The best solution found (or Nothing)
429 computeSolution nl bad_instances depth max_removals min_delta max_delta =
431 removals = computeRemovals nl bad_instances depth
432 removals' = capRemovals removals max_removals
434 solutionFromRemovals removals' min_delta max_delta
438 -- | Compute best table. Note that the ordering of the arguments is important.
439 compareTables :: Table -> Table -> Table
440 compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
441 if a_cv > b_cv then b else a
443 -- | Applies an instance move to a given node list and instance.
444 applyMove :: Node.List -> Instance.Instance
445 -> IMove -> (Maybe Node.List, Instance.Instance, Ndx, Ndx)
447 applyMove nl inst Failover =
448 let old_pdx = Instance.pnode inst
449 old_sdx = Instance.snode inst
450 old_p = Container.find old_pdx nl
451 old_s = Container.find old_sdx nl
452 int_p = Node.removePri old_p inst
453 int_s = Node.removeSec old_s inst
454 new_nl = do -- Maybe monad
455 new_p <- Node.addPri int_s inst
456 new_s <- Node.addSec int_p inst old_sdx
457 return $ Container.addTwo old_pdx new_s old_sdx new_p nl
458 in (new_nl, Instance.setBoth inst old_sdx old_pdx, old_sdx, old_pdx)
460 -- Replace the primary (f:, r:np, f)
461 applyMove nl inst (ReplacePrimary new_pdx) =
462 let old_pdx = Instance.pnode inst
463 old_sdx = Instance.snode inst
464 old_p = Container.find old_pdx nl
465 old_s = Container.find old_sdx nl
466 tgt_n = Container.find new_pdx nl
467 int_p = Node.removePri old_p inst
468 int_s = Node.removeSec old_s inst
469 new_nl = do -- Maybe monad
470 new_p <- Node.addPri tgt_n inst
471 new_s <- Node.addSec int_s inst new_pdx
472 return $ Container.add new_pdx new_p $
473 Container.addTwo old_pdx int_p old_sdx new_s nl
474 in (new_nl, Instance.setPri inst new_pdx, new_pdx, old_sdx)
476 -- Replace the secondary (r:ns)
477 applyMove nl inst (ReplaceSecondary new_sdx) =
478 let old_pdx = Instance.pnode inst
479 old_sdx = Instance.snode inst
480 old_s = Container.find old_sdx nl
481 tgt_n = Container.find new_sdx nl
482 int_s = Node.removeSec old_s inst
483 new_nl = Node.addSec tgt_n inst old_pdx >>=
484 \new_s -> return $ Container.addTwo new_sdx
485 new_s old_sdx int_s nl
486 in (new_nl, Instance.setSec inst new_sdx, old_pdx, new_sdx)
488 -- Replace the secondary and failover (r:np, f)
489 applyMove nl inst (ReplaceAndFailover new_pdx) =
490 let old_pdx = Instance.pnode inst
491 old_sdx = Instance.snode inst
492 old_p = Container.find old_pdx nl
493 old_s = Container.find old_sdx nl
494 tgt_n = Container.find new_pdx nl
495 int_p = Node.removePri old_p inst
496 int_s = Node.removeSec old_s inst
497 new_nl = do -- Maybe monad
498 new_p <- Node.addPri tgt_n inst
499 new_s <- Node.addSec int_p inst new_pdx
500 return $ Container.add new_pdx new_p $
501 Container.addTwo old_pdx new_s old_sdx int_s nl
502 in (new_nl, Instance.setBoth inst new_pdx old_pdx, new_pdx, old_pdx)
504 -- Failver and replace the secondary (f, r:ns)
505 applyMove nl inst (FailoverAndReplace new_sdx) =
506 let old_pdx = Instance.pnode inst
507 old_sdx = Instance.snode inst
508 old_p = Container.find old_pdx nl
509 old_s = Container.find old_sdx nl
510 tgt_n = Container.find new_sdx nl
511 int_p = Node.removePri old_p inst
512 int_s = Node.removeSec old_s inst
513 new_nl = do -- Maybe monad
514 new_p <- Node.addPri int_s inst
515 new_s <- Node.addSec tgt_n inst old_sdx
516 return $ Container.add new_sdx new_s $
517 Container.addTwo old_sdx new_p old_pdx int_p nl
518 in (new_nl, Instance.setBoth inst old_sdx new_sdx, old_sdx, new_sdx)
520 -- | Tries to allocate an instance on one given node.
521 allocateOnSingle :: Node.List -> Instance.Instance -> Node.Node
522 -> (Maybe Node.List, Instance.Instance)
523 allocateOnSingle nl inst p =
524 let new_pdx = Node.idx p
525 new_nl = Node.addPri p inst >>= \new_p ->
526 return $ Container.add new_pdx new_p nl
527 in (new_nl, Instance.setBoth inst new_pdx Node.noSecondary)
529 -- | Tries to allocate an instance on a given pair of nodes.
530 allocateOnPair :: Node.List -> Instance.Instance -> Node.Node -> Node.Node
531 -> (Maybe Node.List, Instance.Instance)
532 allocateOnPair nl inst tgt_p tgt_s =
533 let new_pdx = Node.idx tgt_p
534 new_sdx = Node.idx tgt_s
535 new_nl = do -- Maybe monad
536 new_p <- Node.addPri tgt_p inst
537 new_s <- Node.addSec tgt_s inst new_pdx
538 return $ Container.addTwo new_pdx new_p new_sdx new_s nl
539 in (new_nl, Instance.setBoth inst new_pdx new_sdx)
541 -- | Tries to perform an instance move and returns the best table
542 -- between the original one and the new one.
543 checkSingleStep :: Table -- ^ The original table
544 -> Instance.Instance -- ^ The instance to move
545 -> Table -- ^ The current best table
546 -> IMove -- ^ The move to apply
547 -> Table -- ^ The final best table
548 checkSingleStep ini_tbl target cur_tbl move =
550 Table ini_nl ini_il _ ini_plc = ini_tbl
551 (tmp_nl, new_inst, pri_idx, sec_idx) = applyMove ini_nl target move
553 if isNothing tmp_nl then cur_tbl
555 let tgt_idx = Instance.idx target
556 upd_nl = fromJust tmp_nl
557 upd_cvar = compCV upd_nl
558 upd_il = Container.add tgt_idx new_inst ini_il
559 upd_plc = (tgt_idx, pri_idx, sec_idx, upd_cvar):ini_plc
560 upd_tbl = Table upd_nl upd_il upd_cvar upd_plc
562 compareTables cur_tbl upd_tbl
564 -- | Given the status of the current secondary as a valid new node
565 -- and the current candidate target node,
566 -- generate the possible moves for a instance.
567 possibleMoves :: Bool -> Ndx -> [IMove]
568 possibleMoves True tdx =
569 [ReplaceSecondary tdx,
570 ReplaceAndFailover tdx,
572 FailoverAndReplace tdx]
574 possibleMoves False tdx =
575 [ReplaceSecondary tdx,
576 ReplaceAndFailover tdx]
578 -- | Compute the best move for a given instance.
579 checkInstanceMove :: [Ndx] -- Allowed target node indices
580 -> Table -- Original table
581 -> Instance.Instance -- Instance to move
582 -> Table -- Best new table for this instance
583 checkInstanceMove nodes_idx ini_tbl target =
585 opdx = Instance.pnode target
586 osdx = Instance.snode target
587 nodes = filter (\idx -> idx /= opdx && idx /= osdx) nodes_idx
588 use_secondary = elem osdx nodes_idx
589 aft_failover = if use_secondary -- if allowed to failover
590 then checkSingleStep ini_tbl target ini_tbl Failover
592 all_moves = concatMap (possibleMoves use_secondary) nodes
594 -- iterate over the possible nodes for this instance
595 foldl' (checkSingleStep ini_tbl target) aft_failover all_moves
597 -- | Compute the best next move.
598 checkMove :: [Ndx] -- ^ Allowed target node indices
599 -> Table -- ^ The current solution
600 -> [Instance.Instance] -- ^ List of instances still to move
601 -> Table -- ^ The new solution
602 checkMove nodes_idx ini_tbl victims =
603 let Table _ _ _ ini_plc = ini_tbl
604 -- iterate over all instances, computing the best move
608 if Instance.snode elem == Node.noSecondary then step_tbl
609 else compareTables step_tbl $
610 checkInstanceMove nodes_idx ini_tbl elem)
612 Table _ _ _ best_plc = best_tbl
614 if length best_plc == length ini_plc then -- no advancement
619 -- * Alocation functions
621 -- | Try to allocate an instance on the cluster.
622 tryAlloc :: (Monad m) =>
623 Node.List -- ^ The node list
624 -> Instance.List -- ^ The instance list
625 -> Instance.Instance -- ^ The instance to allocate
626 -> Int -- ^ Required number of nodes
627 -> m [(Maybe Node.List, Instance.Instance, [Node.Node])]
628 -- ^ Possible solution list
629 tryAlloc nl _ inst 2 =
630 let all_nodes = getOnline nl
631 all_pairs = liftM2 (,) all_nodes all_nodes
632 ok_pairs = filter (\(x, y) -> Node.idx x /= Node.idx y) all_pairs
633 sols = map (\(p, s) -> let (mnl, i) = allocateOnPair nl inst p s
638 tryAlloc nl _ inst 1 =
639 let all_nodes = getOnline nl
640 sols = map (\p -> let (mnl, i) = allocateOnSingle nl inst p
645 tryAlloc _ _ _ reqn = fail $ "Unsupported number of alllocation \
646 \destinations required (" ++ (show reqn) ++
647 "), only two supported"
649 -- | Try to allocate an instance on the cluster.
650 tryReloc :: (Monad m) =>
651 Node.List -- ^ The node list
652 -> Instance.List -- ^ The instance list
653 -> Idx -- ^ The index of the instance to move
654 -> Int -- ^ The numver of nodes required
655 -> [Ndx] -- ^ Nodes which should not be used
656 -> m [(Maybe Node.List, Instance.Instance, [Node.Node])]
658 tryReloc nl il xid 1 ex_idx =
659 let all_nodes = getOnline nl
660 inst = Container.find xid il
661 ex_idx' = (Instance.pnode inst):ex_idx
662 valid_nodes = filter (not . flip elem ex_idx' . Node.idx) all_nodes
663 valid_idxes = map Node.idx valid_nodes
664 sols1 = map (\x -> let (mnl, i, _, _) =
665 applyMove nl inst (ReplaceSecondary x)
666 in (mnl, i, [Container.find x nl])
670 tryReloc _ _ _ reqn _ = fail $ "Unsupported number of relocation \
671 \destinations required (" ++ (show reqn) ++
672 "), only one supported"
674 -- * Formatting functions
676 -- | Given the original and final nodes, computes the relocation description.
677 computeMoves :: String -- ^ The instance name
678 -> String -- ^ Original primary
679 -> String -- ^ Original secondary
680 -> String -- ^ New primary
681 -> String -- ^ New secondary
682 -> (String, [String])
683 -- ^ Tuple of moves and commands list; moves is containing
684 -- either @/f/@ for failover or @/r:name/@ for replace
685 -- secondary, while the command list holds gnt-instance
686 -- commands (without that prefix), e.g \"@failover instance1@\"
687 computeMoves i a b c d =
688 if c == a then {- Same primary -}
689 if d == b then {- Same sec??! -}
691 else {- Change of secondary -}
693 [printf "replace-disks -n %s %s" d i])
695 if c == b then {- Failover and ... -}
696 if d == a then {- that's all -}
697 ("f", [printf "migrate -f %s" i])
700 [printf "migrate -f %s" i,
701 printf "replace-disks -n %s %s" d i])
703 if d == a then {- ... and keep primary as secondary -}
705 [printf "replace-disks -n %s %s" c i,
706 printf "migrate -f %s" i])
708 if d == b then {- ... keep same secondary -}
709 (printf "f r:%s f" c,
710 [printf "migrate -f %s" i,
711 printf "replace-disks -n %s %s" c i,
712 printf "migrate -f %s" i])
714 else {- Nothing in common -}
715 (printf "r:%s f r:%s" c d,
716 [printf "replace-disks -n %s %s" c i,
717 printf "migrate -f %s" i,
718 printf "replace-disks -n %s %s" d i])
720 -- | Converts a placement to string format.
721 printSolutionLine :: Node.List -- ^ The node list
722 -> Instance.List -- ^ The instance list
723 -> Int -- ^ Maximum node name length
724 -> Int -- ^ Maximum instance name length
725 -> Placement -- ^ The current placement
726 -> Int -- ^ The index of the placement in
728 -> (String, [String])
729 printSolutionLine nl il nmlen imlen plc pos =
731 pmlen = (2*nmlen + 1)
733 inst = Container.find i il
734 inam = Instance.name inst
735 npri = Container.nameOf nl p
736 nsec = Container.nameOf nl s
737 opri = Container.nameOf nl $ Instance.pnode inst
738 osec = Container.nameOf nl $ Instance.snode inst
739 (moves, cmds) = computeMoves inam opri osec npri nsec
740 ostr = (printf "%s:%s" opri osec)::String
741 nstr = (printf "%s:%s" npri nsec)::String
743 (printf " %3d. %-*s %-*s => %-*s %.8f a=%s"
744 pos imlen inam pmlen ostr
748 -- | Given a list of commands, prefix them with @gnt-instance@ and
749 -- also beautify the display a little.
750 formatCmds :: [[String]] -> String
751 formatCmds cmd_strs =
753 concat $ map (\(a, b) ->
754 (printf "echo step %d" (a::Int)):
756 (map ("gnt-instance " ++) b)) $
759 -- | Converts a solution to string format.
760 printSolution :: Node.List
763 -> ([String], [[String]])
764 printSolution nl il sol =
766 nmlen = Container.maxNameLen nl
767 imlen = Container.maxNameLen il
769 unzip $ map (uncurry $ printSolutionLine nl il nmlen imlen) $
772 -- | Print the node list.
773 printNodes :: Node.List -> String
775 let snl = sortBy (compare `on` Node.idx) (Container.elems nl)
776 m_name = maximum . map (length . Node.name) $ snl
777 helper = Node.list m_name
779 "%2s %-*s %5s %5s %5s %5s %5s %5s %5s %5s %3s %3s %7s %7s"
781 "t_mem" "n_mem" "i_mem" "x_mem" "f_mem" "r_mem"
783 "pri" "sec" "p_fmem" "p_fdsk"
784 in unlines $ (header:map helper snl)
786 -- | Shows statistics for a given node list.
787 printStats :: Node.List -> String
789 let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
790 in printf "f_mem=%.8f, r_mem=%.8f, f_dsk=%.8f, n1=%.3f, uf=%.3f"
791 mem_cv res_cv dsk_cv n1_score off_score