Improve hspace shrinking strategy
[ganeti-local] / src / Ganeti / HTools / Node.hs
1 {-| Module describing a node.
2
3     All updates are functional (copy-based) and return a new node with
4     updated value.
5 -}
6
7 {-
8
9 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
10
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 02110-1301, USA.
25
26 -}
27
28 module Ganeti.HTools.Node
29   ( Node(..)
30   , List
31   -- * Constructor
32   , create
33   -- ** Finalization after data loading
34   , buildPeers
35   , setIdx
36   , setAlias
37   , setOffline
38   , setXmem
39   , setFmem
40   , setPri
41   , setSec
42   , setMaster
43   , setMdsk
44   , setMcpu
45   , setPolicy
46   -- * Tag maps
47   , addTags
48   , delTags
49   , rejectAddTags
50   -- * Instance (re)location
51   , removePri
52   , removeSec
53   , addPri
54   , addPriEx
55   , addSec
56   , addSecEx
57   -- * Stats
58   , availDisk
59   , availMem
60   , availCpu
61   , iMem
62   , iDsk
63   , conflictingPrimaries
64   -- * Formatting
65   , defaultFields
66   , showHeader
67   , showField
68   , list
69   -- * Misc stuff
70   , AssocList
71   , AllocElement
72   , noSecondary
73   , computeGroups
74   , mkNodeGraph
75   ) where
76
77 import Control.Monad (liftM, liftM2)
78 import qualified Data.Foldable as Foldable
79 import Data.Function (on)
80 import qualified Data.Graph as Graph
81 import qualified Data.IntMap as IntMap
82 import Data.List hiding (group)
83 import qualified Data.Map as Map
84 import Data.Ord (comparing)
85 import Text.Printf (printf)
86
87 import qualified Ganeti.HTools.Container as Container
88 import qualified Ganeti.HTools.Instance as Instance
89 import qualified Ganeti.HTools.PeerMap as P
90
91 import Ganeti.BasicTypes
92 import qualified Ganeti.HTools.Types as T
93
94 -- * Type declarations
95
96 -- | The tag map type.
97 type TagMap = Map.Map String Int
98
99 -- | The node type.
100 data Node = Node
101   { name     :: String    -- ^ The node name
102   , alias    :: String    -- ^ The shortened name (for display purposes)
103   , tMem     :: Double    -- ^ Total memory (MiB)
104   , nMem     :: Int       -- ^ Node memory (MiB)
105   , fMem     :: Int       -- ^ Free memory (MiB)
106   , xMem     :: Int       -- ^ Unaccounted memory (MiB)
107   , tDsk     :: Double    -- ^ Total disk space (MiB)
108   , fDsk     :: Int       -- ^ Free disk space (MiB)
109   , tCpu     :: Double    -- ^ Total CPU count
110   , uCpu     :: Int       -- ^ Used VCPU count
111   , spindleCount :: Int   -- ^ Node spindles (spindle_count node parameter)
112   , pList    :: [T.Idx]   -- ^ List of primary instance indices
113   , sList    :: [T.Idx]   -- ^ List of secondary instance indices
114   , idx      :: T.Ndx     -- ^ Internal index for book-keeping
115   , peers    :: P.PeerMap -- ^ Pnode to instance mapping
116   , failN1   :: Bool      -- ^ Whether the node has failed n1
117   , rMem     :: Int       -- ^ Maximum memory needed for failover by
118                           -- primaries of this node
119   , pMem     :: Double    -- ^ Percent of free memory
120   , pDsk     :: Double    -- ^ Percent of free disk
121   , pRem     :: Double    -- ^ Percent of reserved memory
122   , pCpu     :: Double    -- ^ Ratio of virtual to physical CPUs
123   , mDsk     :: Double    -- ^ Minimum free disk ratio
124   , loDsk    :: Int       -- ^ Autocomputed from mDsk low disk
125                           -- threshold
126   , hiCpu    :: Int       -- ^ Autocomputed from mCpu high cpu
127                           -- threshold
128   , hiSpindles :: Double  -- ^ Auto-computed from policy spindle_ratio
129                           -- and the node spindle count
130   , instSpindles :: Double -- ^ Spindles used by instances
131   , offline  :: Bool      -- ^ Whether the node should not be used for
132                           -- allocations and skipped from score
133                           -- computations
134   , isMaster :: Bool      -- ^ Whether the node is the master node
135   , utilPool :: T.DynUtil -- ^ Total utilisation capacity
136   , utilLoad :: T.DynUtil -- ^ Sum of instance utilisation
137   , pTags    :: TagMap    -- ^ Primary instance exclusion tags and their count
138   , group    :: T.Gdx     -- ^ The node's group (index)
139   , iPolicy  :: T.IPolicy -- ^ The instance policy (of the node's group)
140   } deriving (Show, Eq)
141
142 instance T.Element Node where
143   nameOf = name
144   idxOf = idx
145   setAlias = setAlias
146   setIdx = setIdx
147   allNames n = [name n, alias n]
148
149 -- | A simple name for the int, node association list.
150 type AssocList = [(T.Ndx, Node)]
151
152 -- | A simple name for a node map.
153 type List = Container.Container Node
154
155 -- | A simple name for an allocation element (here just for logistic
156 -- reasons).
157 type AllocElement = (List, Instance.Instance, [Node], T.Score)
158
159 -- | Constant node index for a non-moveable instance.
160 noSecondary :: T.Ndx
161 noSecondary = -1
162
163 -- * Helper functions
164
165 -- | Add a tag to a tagmap.
166 addTag :: TagMap -> String -> TagMap
167 addTag t s = Map.insertWith (+) s 1 t
168
169 -- | Add multiple tags.
170 addTags :: TagMap -> [String] -> TagMap
171 addTags = foldl' addTag
172
173 -- | Adjust or delete a tag from a tagmap.
174 delTag :: TagMap -> String -> TagMap
175 delTag t s = Map.update (\v -> if v > 1
176                                  then Just (v-1)
177                                  else Nothing)
178              s t
179
180 -- | Remove multiple tags.
181 delTags :: TagMap -> [String] -> TagMap
182 delTags = foldl' delTag
183
184 -- | Check if we can add a list of tags to a tagmap.
185 rejectAddTags :: TagMap -> [String] -> Bool
186 rejectAddTags t = any (`Map.member` t)
187
188 -- | Check how many primary instances have conflicting tags. The
189 -- algorithm to compute this is to sum the count of all tags, then
190 -- subtract the size of the tag map (since each tag has at least one,
191 -- non-conflicting instance); this is equivalent to summing the
192 -- values in the tag map minus one.
193 conflictingPrimaries :: Node -> Int
194 conflictingPrimaries (Node { pTags = t }) = Foldable.sum t - Map.size t
195
196 -- | Helper function to increment a base value depending on the passed
197 -- boolean argument.
198 incIf :: (Num a) => Bool -> a -> a -> a
199 incIf True  base delta = base + delta
200 incIf False base _     = base
201
202 -- | Helper function to decrement a base value depending on the passed
203 -- boolean argument.
204 decIf :: (Num a) => Bool -> a -> a -> a
205 decIf True  base delta = base - delta
206 decIf False base _     = base
207
208 -- * Initialization functions
209
210 -- | Create a new node.
211 --
212 -- The index and the peers maps are empty, and will be need to be
213 -- update later via the 'setIdx' and 'buildPeers' functions.
214 create :: String -> Double -> Int -> Int -> Double
215        -> Int -> Double -> Bool -> Int -> T.Gdx -> Node
216 create name_init mem_t_init mem_n_init mem_f_init
217        dsk_t_init dsk_f_init cpu_t_init offline_init spindles_init
218        group_init =
219   Node { name = name_init
220        , alias = name_init
221        , tMem = mem_t_init
222        , nMem = mem_n_init
223        , fMem = mem_f_init
224        , tDsk = dsk_t_init
225        , fDsk = dsk_f_init
226        , tCpu = cpu_t_init
227        , spindleCount = spindles_init
228        , uCpu = 0
229        , pList = []
230        , sList = []
231        , failN1 = True
232        , idx = -1
233        , peers = P.empty
234        , rMem = 0
235        , pMem = fromIntegral mem_f_init / mem_t_init
236        , pDsk = computePDsk dsk_f_init dsk_t_init
237        , pRem = 0
238        , pCpu = 0
239        , offline = offline_init
240        , isMaster = False
241        , xMem = 0
242        , mDsk = T.defReservedDiskRatio
243        , loDsk = mDskToloDsk T.defReservedDiskRatio dsk_t_init
244        , hiCpu = mCpuTohiCpu (T.iPolicyVcpuRatio T.defIPolicy) cpu_t_init
245        , hiSpindles = computeHiSpindles (T.iPolicySpindleRatio T.defIPolicy)
246                       spindles_init
247        , instSpindles = 0
248        , utilPool = T.baseUtil
249        , utilLoad = T.zeroUtil
250        , pTags = Map.empty
251        , group = group_init
252        , iPolicy = T.defIPolicy
253        }
254
255 -- | Conversion formula from mDsk\/tDsk to loDsk.
256 mDskToloDsk :: Double -> Double -> Int
257 mDskToloDsk mval = floor . (mval *)
258
259 -- | Conversion formula from mCpu\/tCpu to hiCpu.
260 mCpuTohiCpu :: Double -> Double -> Int
261 mCpuTohiCpu mval = floor . (mval *)
262
263 -- | Conversiojn formula from spindles and spindle ratio to hiSpindles.
264 computeHiSpindles :: Double -> Int -> Double
265 computeHiSpindles spindle_ratio = (spindle_ratio *) . fromIntegral
266
267 -- | Changes the index.
268 --
269 -- This is used only during the building of the data structures.
270 setIdx :: Node -> T.Ndx -> Node
271 setIdx t i = t {idx = i}
272
273 -- | Changes the alias.
274 --
275 -- This is used only during the building of the data structures.
276 setAlias :: Node -> String -> Node
277 setAlias t s = t { alias = s }
278
279 -- | Sets the offline attribute.
280 setOffline :: Node -> Bool -> Node
281 setOffline t val = t { offline = val }
282
283 -- | Sets the master attribute
284 setMaster :: Node -> Bool -> Node
285 setMaster t val = t { isMaster = val }
286
287 -- | Sets the unnaccounted memory.
288 setXmem :: Node -> Int -> Node
289 setXmem t val = t { xMem = val }
290
291 -- | Sets the max disk usage ratio.
292 setMdsk :: Node -> Double -> Node
293 setMdsk t val = t { mDsk = val, loDsk = mDskToloDsk val (tDsk t) }
294
295 -- | Sets the max cpu usage ratio. This will update the node's
296 -- ipolicy, losing sharing (but it should be a seldomly done operation).
297 setMcpu :: Node -> Double -> Node
298 setMcpu t val =
299   let new_ipol = (iPolicy t) { T.iPolicyVcpuRatio = val }
300   in t { hiCpu = mCpuTohiCpu val (tCpu t), iPolicy = new_ipol }
301
302 -- | Sets the policy.
303 setPolicy :: T.IPolicy -> Node -> Node
304 setPolicy pol node =
305   node { iPolicy = pol
306        , hiCpu = mCpuTohiCpu (T.iPolicyVcpuRatio pol) (tCpu node)
307        , hiSpindles = computeHiSpindles (T.iPolicySpindleRatio pol)
308                       (spindleCount node)
309        }
310
311 -- | Computes the maximum reserved memory for peers from a peer map.
312 computeMaxRes :: P.PeerMap -> P.Elem
313 computeMaxRes = P.maxElem
314
315 -- | Builds the peer map for a given node.
316 buildPeers :: Node -> Instance.List -> Node
317 buildPeers t il =
318   let mdata = map
319               (\i_idx -> let inst = Container.find i_idx il
320                              mem = if Instance.usesSecMem inst
321                                      then Instance.mem inst
322                                      else 0
323                          in (Instance.pNode inst, mem))
324               (sList t)
325       pmap = P.accumArray (+) mdata
326       new_rmem = computeMaxRes pmap
327       new_failN1 = fMem t <= new_rmem
328       new_prem = fromIntegral new_rmem / tMem t
329   in t {peers=pmap, failN1 = new_failN1, rMem = new_rmem, pRem = new_prem}
330
331 -- | Calculate the new spindle usage
332 calcSpindleUse :: Node -> Instance.Instance -> Double
333 calcSpindleUse n i = incIf (Instance.usesLocalStorage i) (instSpindles n)
334                        (fromIntegral $ Instance.spindleUse i)
335
336 -- | Assigns an instance to a node as primary and update the used VCPU
337 -- count, utilisation data and tags map.
338 setPri :: Node -> Instance.Instance -> Node
339 setPri t inst = t { pList = Instance.idx inst:pList t
340                   , uCpu = new_count
341                   , pCpu = fromIntegral new_count / tCpu t
342                   , utilLoad = utilLoad t `T.addUtil` Instance.util inst
343                   , pTags = addTags (pTags t) (Instance.exclTags inst)
344                   , instSpindles = calcSpindleUse t inst
345                   }
346   where new_count = Instance.applyIfOnline inst (+ Instance.vcpus inst)
347                     (uCpu t )
348
349 -- | Assigns an instance to a node as secondary without other updates.
350 setSec :: Node -> Instance.Instance -> Node
351 setSec t inst = t { sList = Instance.idx inst:sList t
352                   , utilLoad = old_load { T.dskWeight = T.dskWeight old_load +
353                                           T.dskWeight (Instance.util inst) }
354                   , instSpindles = calcSpindleUse t inst
355                   }
356   where old_load = utilLoad t
357
358 -- | Computes the new 'pDsk' value, handling nodes without local disk
359 -- storage (we consider all their disk used).
360 computePDsk :: Int -> Double -> Double
361 computePDsk _    0     = 1
362 computePDsk used total = fromIntegral used / total
363
364 -- * Update functions
365
366 -- | Sets the free memory.
367 setFmem :: Node -> Int -> Node
368 setFmem t new_mem =
369   let new_n1 = new_mem < rMem t
370       new_mp = fromIntegral new_mem / tMem t
371   in t { fMem = new_mem, failN1 = new_n1, pMem = new_mp }
372
373 -- | Removes a primary instance.
374 removePri :: Node -> Instance.Instance -> Node
375 removePri t inst =
376   let iname = Instance.idx inst
377       i_online = Instance.notOffline inst
378       uses_disk = Instance.usesLocalStorage inst
379       new_plist = delete iname (pList t)
380       new_mem = incIf i_online (fMem t) (Instance.mem inst)
381       new_dsk = incIf uses_disk (fDsk t) (Instance.dsk inst)
382       new_spindles = decIf uses_disk (instSpindles t) 1
383       new_mp = fromIntegral new_mem / tMem t
384       new_dp = computePDsk new_dsk (tDsk t)
385       new_failn1 = new_mem <= rMem t
386       new_ucpu = decIf i_online (uCpu t) (Instance.vcpus inst)
387       new_rcpu = fromIntegral new_ucpu / tCpu t
388       new_load = utilLoad t `T.subUtil` Instance.util inst
389   in t { pList = new_plist, fMem = new_mem, fDsk = new_dsk
390        , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
391        , uCpu = new_ucpu, pCpu = new_rcpu, utilLoad = new_load
392        , pTags = delTags (pTags t) (Instance.exclTags inst)
393        , instSpindles = new_spindles
394        }
395
396 -- | Removes a secondary instance.
397 removeSec :: Node -> Instance.Instance -> Node
398 removeSec t inst =
399   let iname = Instance.idx inst
400       uses_disk = Instance.usesLocalStorage inst
401       cur_dsk = fDsk t
402       pnode = Instance.pNode inst
403       new_slist = delete iname (sList t)
404       new_dsk = incIf uses_disk cur_dsk (Instance.dsk inst)
405       new_spindles = decIf uses_disk (instSpindles t) 1
406       old_peers = peers t
407       old_peem = P.find pnode old_peers
408       new_peem = decIf (Instance.usesSecMem inst) old_peem (Instance.mem inst)
409       new_peers = if new_peem > 0
410                     then P.add pnode new_peem old_peers
411                     else P.remove pnode old_peers
412       old_rmem = rMem t
413       new_rmem = if old_peem < old_rmem
414                    then old_rmem
415                    else computeMaxRes new_peers
416       new_prem = fromIntegral new_rmem / tMem t
417       new_failn1 = fMem t <= new_rmem
418       new_dp = computePDsk new_dsk (tDsk t)
419       old_load = utilLoad t
420       new_load = old_load { T.dskWeight = T.dskWeight old_load -
421                                           T.dskWeight (Instance.util inst) }
422   in t { sList = new_slist, fDsk = new_dsk, peers = new_peers
423        , failN1 = new_failn1, rMem = new_rmem, pDsk = new_dp
424        , pRem = new_prem, utilLoad = new_load
425        , instSpindles = new_spindles
426        }
427
428 -- | Adds a primary instance (basic version).
429 addPri :: Node -> Instance.Instance -> T.OpResult Node
430 addPri = addPriEx False
431
432 -- | Adds a primary instance (extended version).
433 addPriEx :: Bool               -- ^ Whether to override the N+1 and
434                                -- other /soft/ checks, useful if we
435                                -- come from a worse status
436                                -- (e.g. offline)
437          -> Node               -- ^ The target node
438          -> Instance.Instance  -- ^ The instance to add
439          -> T.OpResult Node    -- ^ The result of the operation,
440                                -- either the new version of the node
441                                -- or a failure mode
442 addPriEx force t inst =
443   let iname = Instance.idx inst
444       i_online = Instance.notOffline inst
445       uses_disk = Instance.usesLocalStorage inst
446       cur_dsk = fDsk t
447       new_mem = decIf i_online (fMem t) (Instance.mem inst)
448       new_dsk = decIf uses_disk cur_dsk (Instance.dsk inst)
449       new_spindles = incIf uses_disk (instSpindles t) 1
450       new_failn1 = new_mem <= rMem t
451       new_ucpu = incIf i_online (uCpu t) (Instance.vcpus inst)
452       new_pcpu = fromIntegral new_ucpu / tCpu t
453       new_dp = computePDsk new_dsk (tDsk t)
454       l_cpu = T.iPolicyVcpuRatio $ iPolicy t
455       new_load = utilLoad t `T.addUtil` Instance.util inst
456       inst_tags = Instance.exclTags inst
457       old_tags = pTags t
458       strict = not force
459   in case () of
460        _ | new_mem <= 0 -> Bad T.FailMem
461          | uses_disk && new_dsk <= 0 -> Bad T.FailDisk
462          | uses_disk && mDsk t > new_dp && strict -> Bad T.FailDisk
463          | uses_disk && new_spindles > hiSpindles t
464              && strict -> Bad T.FailDisk
465          | new_failn1 && not (failN1 t) && strict -> Bad T.FailMem
466          | l_cpu >= 0 && l_cpu < new_pcpu && strict -> Bad T.FailCPU
467          | rejectAddTags old_tags inst_tags -> Bad T.FailTags
468          | otherwise ->
469            let new_plist = iname:pList t
470                new_mp = fromIntegral new_mem / tMem t
471                r = t { pList = new_plist, fMem = new_mem, fDsk = new_dsk
472                      , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
473                      , uCpu = new_ucpu, pCpu = new_pcpu
474                      , utilLoad = new_load
475                      , pTags = addTags old_tags inst_tags
476                      , instSpindles = new_spindles
477                      }
478            in Ok r
479
480 -- | Adds a secondary instance (basic version).
481 addSec :: Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
482 addSec = addSecEx False
483
484 -- | Adds a secondary instance (extended version).
485 addSecEx :: Bool -> Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
486 addSecEx force t inst pdx =
487   let iname = Instance.idx inst
488       old_peers = peers t
489       old_mem = fMem t
490       new_dsk = fDsk t - Instance.dsk inst
491       new_spindles = instSpindles t + 1
492       secondary_needed_mem = if Instance.usesSecMem inst
493                                then Instance.mem inst
494                                else 0
495       new_peem = P.find pdx old_peers + secondary_needed_mem
496       new_peers = P.add pdx new_peem old_peers
497       new_rmem = max (rMem t) new_peem
498       new_prem = fromIntegral new_rmem / tMem t
499       new_failn1 = old_mem <= new_rmem
500       new_dp = computePDsk new_dsk (tDsk t)
501       old_load = utilLoad t
502       new_load = old_load { T.dskWeight = T.dskWeight old_load +
503                                           T.dskWeight (Instance.util inst) }
504       strict = not force
505   in case () of
506        _ | not (Instance.hasSecondary inst) -> Bad T.FailDisk
507          | new_dsk <= 0 -> Bad T.FailDisk
508          | mDsk t > new_dp && strict -> Bad T.FailDisk
509          | new_spindles > hiSpindles t && strict -> Bad T.FailDisk
510          | secondary_needed_mem >= old_mem && strict -> Bad T.FailMem
511          | new_failn1 && not (failN1 t) && strict -> Bad T.FailMem
512          | otherwise ->
513            let new_slist = iname:sList t
514                r = t { sList = new_slist, fDsk = new_dsk
515                      , peers = new_peers, failN1 = new_failn1
516                      , rMem = new_rmem, pDsk = new_dp
517                      , pRem = new_prem, utilLoad = new_load
518                      , instSpindles = new_spindles
519                      }
520            in Ok r
521
522 -- * Stats functions
523
524 -- | Computes the amount of available disk on a given node.
525 availDisk :: Node -> Int
526 availDisk t =
527   let _f = fDsk t
528       _l = loDsk t
529   in if _f < _l
530        then 0
531        else _f - _l
532
533 -- | Computes the amount of used disk on a given node.
534 iDsk :: Node -> Int
535 iDsk t = truncate (tDsk t) - fDsk t
536
537 -- | Computes the amount of available memory on a given node.
538 availMem :: Node -> Int
539 availMem t =
540   let _f = fMem t
541       _l = rMem t
542   in if _f < _l
543        then 0
544        else _f - _l
545
546 -- | Computes the amount of available memory on a given node.
547 availCpu :: Node -> Int
548 availCpu t =
549   let _u = uCpu t
550       _l = hiCpu t
551   in if _l >= _u
552        then _l - _u
553        else 0
554
555 -- | The memory used by instances on a given node.
556 iMem :: Node -> Int
557 iMem t = truncate (tMem t) - nMem t - xMem t - fMem t
558
559 -- * Node graph functions
560 -- These functions do the transformations needed so that nodes can be
561 -- represented as a graph connected by the instances that are replicated
562 -- on them.
563
564 -- * Making of a Graph from a node/instance list
565
566 -- | Transform an instance into a list of edges on the node graph
567 instanceToEdges :: Instance.Instance -> [Graph.Edge]
568 instanceToEdges i
569   | Instance.hasSecondary i = [(pnode,snode), (snode,pnode)]
570   | otherwise = []
571     where pnode = Instance.pNode i
572           snode = Instance.sNode i
573
574 -- | Transform the list of instances into list of destination edges
575 instancesToEdges :: Instance.List -> [Graph.Edge]
576 instancesToEdges = concatMap instanceToEdges . Container.elems
577
578 -- | Transform the list of nodes into vertices bounds.
579 -- Returns Nothing is the list is empty.
580 nodesToBounds :: List -> Maybe Graph.Bounds
581 nodesToBounds nl = liftM2 (,) nmin nmax
582     where nmin = fmap (fst . fst) (IntMap.minViewWithKey nl)
583           nmax = fmap (fst . fst) (IntMap.maxViewWithKey nl)
584
585 -- | Transform a Node + Instance list into a NodeGraph type.
586 -- Returns Nothing if the node list is empty.
587 mkNodeGraph :: List -> Instance.List -> Maybe Graph.Graph
588 mkNodeGraph nl il =
589   liftM (`Graph.buildG` instancesToEdges il) (nodesToBounds nl)
590
591 -- * Display functions
592
593 -- | Return a field for a given node.
594 showField :: Node   -- ^ Node which we're querying
595           -> String -- ^ Field name
596           -> String -- ^ Field value as string
597 showField t field =
598   case field of
599     "idx"  -> printf "%4d" $ idx t
600     "name" -> alias t
601     "fqdn" -> name t
602     "status" -> case () of
603                   _ | offline t -> "-"
604                     | failN1 t -> "*"
605                     | otherwise -> " "
606     "tmem" -> printf "%5.0f" $ tMem t
607     "nmem" -> printf "%5d" $ nMem t
608     "xmem" -> printf "%5d" $ xMem t
609     "fmem" -> printf "%5d" $ fMem t
610     "imem" -> printf "%5d" $ iMem t
611     "rmem" -> printf "%5d" $ rMem t
612     "amem" -> printf "%5d" $ fMem t - rMem t
613     "tdsk" -> printf "%5.0f" $ tDsk t / 1024
614     "fdsk" -> printf "%5d" $ fDsk t `div` 1024
615     "tcpu" -> printf "%4.0f" $ tCpu t
616     "ucpu" -> printf "%4d" $ uCpu t
617     "pcnt" -> printf "%3d" $ length (pList t)
618     "scnt" -> printf "%3d" $ length (sList t)
619     "plist" -> show $ pList t
620     "slist" -> show $ sList t
621     "pfmem" -> printf "%6.4f" $ pMem t
622     "pfdsk" -> printf "%6.4f" $ pDsk t
623     "rcpu"  -> printf "%5.2f" $ pCpu t
624     "cload" -> printf "%5.3f" uC
625     "mload" -> printf "%5.3f" uM
626     "dload" -> printf "%5.3f" uD
627     "nload" -> printf "%5.3f" uN
628     "ptags" -> intercalate "," . map (uncurry (printf "%s=%d")) .
629                Map.toList $ pTags t
630     "peermap" -> show $ peers t
631     "spindle_count" -> show $ spindleCount t
632     "hi_spindles" -> show $ hiSpindles t
633     "inst_spindles" -> show $ instSpindles t
634     _ -> T.unknownField
635   where
636     T.DynUtil { T.cpuWeight = uC, T.memWeight = uM,
637                 T.dskWeight = uD, T.netWeight = uN } = utilLoad t
638
639 -- | Returns the header and numeric propery of a field.
640 showHeader :: String -> (String, Bool)
641 showHeader field =
642   case field of
643     "idx" -> ("Index", True)
644     "name" -> ("Name", False)
645     "fqdn" -> ("Name", False)
646     "status" -> ("F", False)
647     "tmem" -> ("t_mem", True)
648     "nmem" -> ("n_mem", True)
649     "xmem" -> ("x_mem", True)
650     "fmem" -> ("f_mem", True)
651     "imem" -> ("i_mem", True)
652     "rmem" -> ("r_mem", True)
653     "amem" -> ("a_mem", True)
654     "tdsk" -> ("t_dsk", True)
655     "fdsk" -> ("f_dsk", True)
656     "tcpu" -> ("pcpu", True)
657     "ucpu" -> ("vcpu", True)
658     "pcnt" -> ("pcnt", True)
659     "scnt" -> ("scnt", True)
660     "plist" -> ("primaries", True)
661     "slist" -> ("secondaries", True)
662     "pfmem" -> ("p_fmem", True)
663     "pfdsk" -> ("p_fdsk", True)
664     "rcpu"  -> ("r_cpu", True)
665     "cload" -> ("lCpu", True)
666     "mload" -> ("lMem", True)
667     "dload" -> ("lDsk", True)
668     "nload" -> ("lNet", True)
669     "ptags" -> ("PrimaryTags", False)
670     "peermap" -> ("PeerMap", False)
671     "spindle_count" -> ("NodeSpindles", True)
672     "hi_spindles" -> ("MaxSpindles", True)
673     "inst_spindles" -> ("InstSpindles", True)
674     -- TODO: add node fields (group.uuid, group)
675     _ -> (T.unknownField, False)
676
677 -- | String converter for the node list functionality.
678 list :: [String] -> Node -> [String]
679 list fields t = map (showField t) fields
680
681 -- | Constant holding the fields we're displaying by default.
682 defaultFields :: [String]
683 defaultFields =
684   [ "status", "name", "tmem", "nmem", "imem", "xmem", "fmem"
685   , "rmem", "tdsk", "fdsk", "tcpu", "ucpu", "pcnt", "scnt"
686   , "pfmem", "pfdsk", "rcpu"
687   , "cload", "mload", "dload", "nload" ]
688
689 {-# ANN computeGroups "HLint: ignore Use alternative" #-}
690 -- | Split a list of nodes into a list of (node group UUID, list of
691 -- associated nodes).
692 computeGroups :: [Node] -> [(T.Gdx, [Node])]
693 computeGroups nodes =
694   let nodes' = sortBy (comparing group) nodes
695       nodes'' = groupBy ((==) `on` group) nodes'
696   -- use of head here is OK, since groupBy returns non-empty lists; if
697   -- you remove groupBy, also remove use of head
698   in map (\nl -> (group (head nl), nl)) nodes''