Add display of more stats in hspace
[ganeti-local] / hscan.hs
1 {-| Scan clusters via RAPI and write instance/node data files.
2
3 -}
4
5 {-
6
7 Copyright (C) 2009 Google Inc.
8
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.
13
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.
18
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
22 02110-1301, USA.
23
24 -}
25
26 module Main (main) where
27
28 import Data.List
29 import Data.Function
30 import Monad
31 import System
32 import System.IO
33 import System.FilePath
34 import System.Console.GetOpt
35 import qualified System
36
37 import Text.Printf (printf)
38
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
43 import qualified Ganeti.HTools.CLI as CLI
44 import qualified Ganeti.HTools.Rapi as Rapi
45 import qualified Ganeti.HTools.Loader as Loader
46 import Ganeti.HTools.Types
47
48 -- | Command line options structure.
49 data Options = Options
50     { optShowNodes :: Bool     -- ^ Whether to show node status
51     , optOutPath   :: FilePath -- ^ Path to the output directory
52     , optVerbose   :: Int      -- ^ Verbosity level
53     , optNoHeader  :: Bool     -- ^ Do not show a header line
54     , optShowVer   :: Bool     -- ^ Just show the program version
55     , optShowHelp  :: Bool     -- ^ Just show the help
56     } deriving Show
57
58 instance CLI.CLIOptions Options where
59     showVersion = optShowVer
60     showHelp    = optShowHelp
61
62 -- | Default values for the command line options.
63 defaultOptions :: Options
64 defaultOptions  = Options
65  { optShowNodes = False
66  , optOutPath   = "."
67  , optVerbose   = 0
68  , optNoHeader  = False
69  , optShowVer   = False
70  , optShowHelp  = False
71  }
72
73 -- | Options list and functions
74 options :: [OptDescr (Options -> Options)]
75 options =
76     [ Option ['p']     ["print-nodes"]
77       (NoArg (\ opts -> opts { optShowNodes = True }))
78       "print the final node list"
79     , Option ['d']     ["output-dir"]
80       (ReqArg (\ d opts -> opts { optOutPath = d }) "PATH")
81       "directory in which to write output files"
82     , Option ['v']     ["verbose"]
83       (NoArg (\ opts -> opts { optVerbose = (optVerbose opts) + 1 }))
84       "increase the verbosity level"
85     , Option []        ["no-headers"]
86       (NoArg (\ opts -> opts { optNoHeader = True }))
87       "do not show a header line"
88     , Option ['V']     ["version"]
89       (NoArg (\ opts -> opts { optShowVer = True}))
90       "show the version of the program"
91     , Option ['h']     ["help"]
92       (NoArg (\ opts -> opts { optShowHelp = True}))
93       "show help"
94     ]
95
96 -- | Serialize a single node
97 serializeNode :: String -> Node.Node -> String
98 serializeNode csf node =
99     printf "%s|%.0f|%d|%d|%.0f|%d|%.0f|%c" (Node.name node ++ csf)
100                (Node.t_mem node) (Node.n_mem node) (Node.f_mem node)
101                (Node.t_dsk node) (Node.f_dsk node) (Node.t_cpu node)
102                (if Node.offline node then 'Y' else 'N')
103
104 -- | Generate node file data from node objects
105 serializeNodes :: String -> Node.List -> String
106 serializeNodes csf =
107     unlines . map (serializeNode csf) . Container.elems
108
109 -- | Serialize a single instance
110 serializeInstance :: String -> Node.List -> Instance.Instance -> String
111 serializeInstance csf nl inst =
112     let
113         iname = Instance.name inst ++ csf
114         pnode = (Container.nameOf nl $ Instance.pnode inst) ++ csf
115         sidx = Instance.snode inst
116         snode = (if sidx == Node.noSecondary
117                     then ""
118                     else (Container.nameOf nl sidx) ++ csf)
119     in
120       printf "%s|%d|%d|%d|%s|%s|%s"
121              iname (Instance.mem inst) (Instance.dsk inst)
122              (Instance.vcpus inst) (Instance.run_st inst)
123              pnode snode
124
125 -- | Generate instance file data from instance objects
126 serializeInstances :: String -> Node.List -> Instance.List -> String
127 serializeInstances csf nl =
128     unlines . map (serializeInstance csf nl) . Container.elems
129
130 -- | Return a one-line summary of cluster state
131 printCluster :: Node.List -> Instance.List
132              -> String
133 printCluster nl il =
134     let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il
135         ccv = Cluster.compCV nl
136         nodes = Container.elems nl
137         insts = Container.elems il
138         t_ram = sum . map Node.t_mem $ nodes
139         t_dsk = sum . map Node.t_dsk $ nodes
140         f_ram = sum . map Node.f_mem $ nodes
141         f_dsk = sum . map Node.f_dsk $ nodes
142     in
143       printf "%5d %5d %5d %5d %6.0f %6d %6.0f %6d %.8f"
144                  (length nodes) (length insts)
145                  (length bad_nodes) (length bad_instances)
146                  t_ram f_ram
147                  (t_dsk / 1024) (f_dsk `div` 1024)
148                  ccv
149
150
151 -- | Replace slashes with underscore for saving to filesystem
152
153 fixSlash :: String -> String
154 fixSlash = map (\x -> if x == '/' then '_' else x)
155
156 -- | Main function.
157 main :: IO ()
158 main = do
159   cmd_args <- System.getArgs
160   (opts, clusters) <- CLI.parseOpts cmd_args "hscan" options
161                       defaultOptions
162
163   let odir = optOutPath opts
164       nlen = maximum . map length $ clusters
165
166   unless (optNoHeader opts) $
167          printf "%-*s %5s %5s %5s %5s %6s %6s %6s %6s %10s\n" nlen
168                 "Name" "Nodes" "Inst" "BNode" "BInst" "t_mem" "f_mem"
169                 "t_disk" "f_disk" "Score"
170
171   mapM_ (\ name ->
172             do
173               printf "%-*s " nlen name
174               hFlush stdout
175               input_data <- Rapi.loadData name
176               let ldresult = input_data >>= Loader.mergeData
177               (case ldresult of
178                  Bad err -> printf "\nError: failed to load data. \
179                                    \Details:\n%s\n" err
180                  Ok x -> do
181                    let (nl, il, csf) = x
182                        (_, fix_nl) = Loader.checkData nl il
183                    putStrLn $ printCluster fix_nl il
184                    when (optShowNodes opts) $ do
185                            putStr $ Cluster.printNodes fix_nl
186                    let ndata = serializeNodes csf nl
187                        idata = serializeInstances csf nl il
188                        oname = odir </> (fixSlash name)
189                    writeFile (oname <.> "nodes") ndata
190                    writeFile (oname <.> "instances") idata)
191        ) clusters