Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Utils.hs @ 30dd3377

History | View | Annotate | Download (14.9 kB)

1
{-| Utility functions. -}
2

    
3
{-
4

    
5
Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
6

    
7
This program is free software; you can redistribute it and/or modify
8
it under the terms of the GNU General Public License as published by
9
the Free Software Foundation; either version 2 of the License, or
10
(at your option) any later version.
11

    
12
This program is distributed in the hope that it will be useful, but
13
WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
General Public License for more details.
16

    
17
You should have received a copy of the GNU General Public License
18
along with this program; if not, write to the Free Software
19
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20
02110-1301, USA.
21

    
22
-}
23

    
24
module Ganeti.Utils
25
  ( debug
26
  , debugFn
27
  , debugXy
28
  , sepSplit
29
  , stdDev
30
  , if'
31
  , select
32
  , applyIf
33
  , commaJoin
34
  , ensureQuoted
35
  , tryRead
36
  , formatTable
37
  , printTable
38
  , parseUnit
39
  , plural
40
  , niceSort
41
  , niceSortKey
42
  , exitIfBad
43
  , exitErr
44
  , exitWhen
45
  , exitUnless
46
  , rStripSpace
47
  , newUUID
48
  , getCurrentTime
49
  , getCurrentTimeUSec
50
  , clockTimeToString
51
  , chompPrefix
52
  , warn
53
  , wrap
54
  , trim
55
  , defaultHead
56
  , exitIfEmpty
57
  , splitEithers
58
  , recombineEithers
59
  ) where
60

    
61
import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
62
import Data.Function (on)
63
import Data.List
64
import Control.Monad (foldM)
65

    
66
import Debug.Trace
67

    
68
import Ganeti.BasicTypes
69
import qualified Ganeti.Constants as C
70
import System.IO
71
import System.Exit
72
import System.Time
73

    
74
-- * Debug functions
75

    
76
-- | To be used only for debugging, breaks referential integrity.
77
debug :: Show a => a -> a
78
debug x = trace (show x) x
79

    
80
-- | Displays a modified form of the second parameter before returning
81
-- it.
82
debugFn :: Show b => (a -> b) -> a -> a
83
debugFn fn x = debug (fn x) `seq` x
84

    
85
-- | Show the first parameter before returning the second one.
86
debugXy :: Show a => a -> b -> b
87
debugXy = seq . debug
88

    
89
-- * Miscellaneous
90

    
91
-- | Apply the function if condition holds, otherwise use default value.
92
applyIf :: Bool -> (a -> a) -> a -> a
93
applyIf b f x = if b then f x else x
94

    
95
-- | Comma-join a string list.
96
commaJoin :: [String] -> String
97
commaJoin = intercalate ","
98

    
99
-- | Split a list on a separator and return an array.
100
sepSplit :: Eq a => a -> [a] -> [[a]]
101
sepSplit sep s
102
  | null s    = []
103
  | null xs   = [x]
104
  | null ys   = [x,[]]
105
  | otherwise = x:sepSplit sep ys
106
  where (x, xs) = break (== sep) s
107
        ys = drop 1 xs
108

    
109
-- | Simple pluralize helper
110
plural :: Int -> String -> String -> String
111
plural 1 s _ = s
112
plural _ _ p = p
113

    
114
-- | Ensure a value is quoted if needed.
115
ensureQuoted :: String -> String
116
ensureQuoted v = if not (all (\c -> isAlphaNum c || c == '.') v)
117
                 then '\'':v ++ "'"
118
                 else v
119

    
120
-- * Mathematical functions
121

    
122
-- Simple and slow statistical functions, please replace with better
123
-- versions
124

    
125
-- | Standard deviation function.
126
stdDev :: [Double] -> Double
127
stdDev lst =
128
  -- first, calculate the list length and sum lst in a single step,
129
  -- for performance reasons
130
  let (ll', sx) = foldl' (\(rl, rs) e ->
131
                           let rl' = rl + 1
132
                               rs' = rs + e
133
                           in rl' `seq` rs' `seq` (rl', rs')) (0::Int, 0) lst
134
      ll = fromIntegral ll'::Double
135
      mv = sx / ll
136
      av = foldl' (\accu em -> let d = em - mv in accu + d * d) 0.0 lst
137
  in sqrt (av / ll) -- stddev
138

    
139
-- *  Logical functions
140

    
141
-- Avoid syntactic sugar and enhance readability. These functions are proposed
142
-- by some for inclusion in the Prelude, and at the moment they are present
143
-- (with various definitions) in the utility-ht package. Some rationale and
144
-- discussion is available at <http://www.haskell.org/haskellwiki/If-then-else>
145

    
146
-- | \"if\" as a function, rather than as syntactic sugar.
147
if' :: Bool -- ^ condition
148
    -> a    -- ^ \"then\" result
149
    -> a    -- ^ \"else\" result
150
    -> a    -- ^ \"then\" or "else" result depending on the condition
151
if' True x _ = x
152
if' _    _ y = y
153

    
154
-- * Parsing utility functions
155

    
156
-- | Parse results from readsPrec.
157
parseChoices :: (Monad m, Read a) => String -> String -> [(a, String)] -> m a
158
parseChoices _ _ ((v, ""):[]) = return v
159
parseChoices name s ((_, e):[]) =
160
    fail $ name ++ ": leftover characters when parsing '"
161
           ++ s ++ "': '" ++ e ++ "'"
162
parseChoices name s _ = fail $ name ++ ": cannot parse string '" ++ s ++ "'"
163

    
164
-- | Safe 'read' function returning data encapsulated in a Result.
165
tryRead :: (Monad m, Read a) => String -> String -> m a
166
tryRead name s = parseChoices name s $ reads s
167

    
168
-- | Format a table of strings to maintain consistent length.
169
formatTable :: [[String]] -> [Bool] -> [[String]]
170
formatTable vals numpos =
171
    let vtrans = transpose vals  -- transpose, so that we work on rows
172
                                 -- rather than columns
173
        mlens = map (maximum . map length) vtrans
174
        expnd = map (\(flds, isnum, ml) ->
175
                         map (\val ->
176
                                  let delta = ml - length val
177
                                      filler = replicate delta ' '
178
                                  in if delta > 0
179
                                     then if isnum
180
                                          then filler ++ val
181
                                          else val ++ filler
182
                                     else val
183
                             ) flds
184
                    ) (zip3 vtrans numpos mlens)
185
   in transpose expnd
186

    
187
-- | Constructs a printable table from given header and rows
188
printTable :: String -> [String] -> [[String]] -> [Bool] -> String
189
printTable lp header rows isnum =
190
  unlines . map ((++) lp . (:) ' ' . unwords) $
191
  formatTable (header:rows) isnum
192

    
193
-- | Converts a unit (e.g. m or GB) into a scaling factor.
194
parseUnitValue :: (Monad m) => String -> m Rational
195
parseUnitValue unit
196
  -- binary conversions first
197
  | null unit                     = return 1
198
  | unit == "m" || upper == "MIB" = return 1
199
  | unit == "g" || upper == "GIB" = return kbBinary
200
  | unit == "t" || upper == "TIB" = return $ kbBinary * kbBinary
201
  -- SI conversions
202
  | unit == "M" || upper == "MB"  = return mbFactor
203
  | unit == "G" || upper == "GB"  = return $ mbFactor * kbDecimal
204
  | unit == "T" || upper == "TB"  = return $ mbFactor * kbDecimal * kbDecimal
205
  | otherwise = fail $ "Unknown unit '" ++ unit ++ "'"
206
  where upper = map toUpper unit
207
        kbBinary = 1024 :: Rational
208
        kbDecimal = 1000 :: Rational
209
        decToBin = kbDecimal / kbBinary -- factor for 1K conversion
210
        mbFactor = decToBin * decToBin -- twice the factor for just 1K
211

    
212
-- | Tries to extract number and scale from the given string.
213
--
214
-- Input must be in the format NUMBER+ SPACE* [UNIT]. If no unit is
215
-- specified, it defaults to MiB. Return value is always an integral
216
-- value in MiB.
217
parseUnit :: (Monad m, Integral a, Read a) => String -> m a
218
parseUnit str =
219
  -- TODO: enhance this by splitting the unit parsing code out and
220
  -- accepting floating-point numbers
221
  case (reads str::[(Int, String)]) of
222
    [(v, suffix)] ->
223
      let unit = dropWhile (== ' ') suffix
224
      in do
225
        scaling <- parseUnitValue unit
226
        return $ truncate (fromIntegral v * scaling)
227
    _ -> fail $ "Can't parse string '" ++ str ++ "'"
228

    
229
-- | Unwraps a 'Result', exiting the program if it is a 'Bad' value,
230
-- otherwise returning the actual contained value.
231
exitIfBad :: String -> Result a -> IO a
232
exitIfBad msg (Bad s) = exitErr (msg ++ ": " ++ s)
233
exitIfBad _ (Ok v) = return v
234

    
235
-- | Exits immediately with an error message.
236
exitErr :: String -> IO a
237
exitErr errmsg = do
238
  hPutStrLn stderr $ "Error: " ++ errmsg
239
  exitWith (ExitFailure 1)
240

    
241
-- | Exits with an error message if the given boolean condition if true.
242
exitWhen :: Bool -> String -> IO ()
243
exitWhen True msg = exitErr msg
244
exitWhen False _  = return ()
245

    
246
-- | Exits with an error message /unless/ the given boolean condition
247
-- if true, the opposite of 'exitWhen'.
248
exitUnless :: Bool -> String -> IO ()
249
exitUnless cond = exitWhen (not cond)
250

    
251
-- | Print a warning, but do not exit.
252
warn :: String -> IO ()
253
warn = hPutStrLn stderr . (++) "Warning: "
254

    
255
-- | Helper for 'niceSort'. Computes the key element for a given string.
256
extractKey :: [Either Integer String]  -- ^ Current (partial) key, reversed
257
           -> String                   -- ^ Remaining string
258
           -> ([Either Integer String], String)
259
extractKey ek [] = (reverse ek, [])
260
extractKey ek xs@(x:_) =
261
  let (span_fn, conv_fn) = if isDigit x
262
                             then (isDigit, Left . read)
263
                             else (not . isDigit, Right)
264
      (k, rest) = span span_fn xs
265
  in extractKey (conv_fn k:ek) rest
266

    
267
{-| Sort a list of strings based on digit and non-digit groupings.
268

    
269
Given a list of names @['a1', 'a10', 'a11', 'a2']@ this function
270
will sort the list in the logical order @['a1', 'a2', 'a10', 'a11']@.
271

    
272
The sort algorithm breaks each name in groups of either only-digits or
273
no-digits, and sorts based on each group.
274

    
275
Internally, this is not implemented via regexes (like the Python
276
version), but via actual splitting of the string in sequences of
277
either digits or everything else, and converting the digit sequences
278
in /Left Integer/ and the non-digit ones in /Right String/, at which
279
point sorting becomes trivial due to the built-in 'Either' ordering;
280
we only need one extra step of dropping the key at the end.
281

    
282
-}
283
niceSort :: [String] -> [String]
284
niceSort = niceSortKey id
285

    
286
-- | Key-version of 'niceSort'. We use 'sortBy' and @compare `on` fst@
287
-- since we don't want to add an ordering constraint on the /a/ type,
288
-- hence the need to only compare the first element of the /(key, a)/
289
-- tuple.
290
niceSortKey :: (a -> String) -> [a] -> [a]
291
niceSortKey keyfn =
292
  map snd . sortBy (compare `on` fst) .
293
  map (\s -> (fst . extractKey [] $ keyfn s, s))
294

    
295
-- | Strip space characthers (including newline). As this is
296
-- expensive, should only be run on small strings.
297
rStripSpace :: String -> String
298
rStripSpace = reverse . dropWhile isSpace . reverse
299

    
300
-- | Returns a random UUID.
301
-- This is a Linux-specific method as it uses the /proc filesystem.
302
newUUID :: IO String
303
newUUID = do
304
  contents <- readFile C.randomUuidFile
305
  return $! rStripSpace $ take 128 contents
306

    
307
-- | Returns the current time as an 'Integer' representing the number
308
-- of seconds from the Unix epoch.
309
getCurrentTime :: IO Integer
310
getCurrentTime = do
311
  TOD ctime _ <- getClockTime
312
  return ctime
313

    
314
-- | Returns the current time as an 'Integer' representing the number
315
-- of microseconds from the Unix epoch (hence the need for 'Integer').
316
getCurrentTimeUSec :: IO Integer
317
getCurrentTimeUSec = do
318
  TOD ctime pico <- getClockTime
319
  -- pico: 10^-12, micro: 10^-6, so we have to shift seconds left and
320
  -- picoseconds right
321
  return $ ctime * 1000000 + pico `div` 1000000
322

    
323
-- | Convert a ClockTime into a (seconds-only) timestamp.
324
clockTimeToString :: ClockTime -> String
325
clockTimeToString (TOD t _) = show t
326

    
327
{-| Strip a prefix from a string, allowing the last character of the prefix
328
(which is assumed to be a separator) to be absent from the string if the string
329
terminates there.
330

    
331
\>>> chompPrefix \"foo:bar:\" \"a:b:c\"
332
Nothing
333

    
334
\>>> chompPrefix \"foo:bar:\" \"foo:bar:baz\"
335
Just \"baz\"
336

    
337
\>>> chompPrefix \"foo:bar:\" \"foo:bar:\"
338
Just \"\"
339

    
340
\>>> chompPrefix \"foo:bar:\" \"foo:bar\"
341
Just \"\"
342

    
343
\>>> chompPrefix \"foo:bar:\" \"foo:barbaz\"
344
Nothing
345
-}
346
chompPrefix :: String -> String -> Maybe String
347
chompPrefix pfx str =
348
  if pfx `isPrefixOf` str || str == init pfx
349
    then Just $ drop (length pfx) str
350
    else Nothing
351

    
352
-- | Breaks a string in lines with length \<= maxWidth.
353
--
354
-- NOTE: The split is OK if:
355
--
356
-- * It doesn't break a word, i.e. the next line begins with space
357
--   (@isSpace . head $ rest@) or the current line ends with space
358
--   (@null revExtra@);
359
--
360
-- * It breaks a very big word that doesn't fit anyway (@null revLine@).
361
wrap :: Int      -- ^ maxWidth
362
     -> String   -- ^ string that needs wrapping
363
     -> [String] -- ^ string \"broken\" in lines
364
wrap maxWidth = filter (not . null) . map trim . wrap0
365
  where wrap0 :: String -> [String]
366
        wrap0 text
367
          | length text <= maxWidth = [text]
368
          | isSplitOK               = line : wrap0 rest
369
          | otherwise               = line' : wrap0 rest'
370
          where (line, rest) = splitAt maxWidth text
371
                (revExtra, revLine) = break isSpace . reverse $ line
372
                (line', rest') = (reverse revLine, reverse revExtra ++ rest)
373
                isSplitOK =
374
                  null revLine || null revExtra || startsWithSpace rest
375
                startsWithSpace (x:_) = isSpace x
376
                startsWithSpace _     = False
377

    
378
-- | Removes surrounding whitespace. Should only be used in small
379
-- strings.
380
trim :: String -> String
381
trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
382

    
383
-- | A safer head version, with a default value.
384
defaultHead :: a -> [a] -> a
385
defaultHead def []    = def
386
defaultHead _   (x:_) = x
387

    
388
-- | A 'head' version in the I/O monad, for validating parameters
389
-- without which we cannot continue.
390
exitIfEmpty :: String -> [a] -> IO a
391
exitIfEmpty _ (x:_) = return x
392
exitIfEmpty s []    = exitErr s
393

    
394
-- | Split an 'Either' list into two separate lists (containing the
395
-- 'Left' and 'Right' elements, plus a \"trail\" list that allows
396
-- recombination later.
397
--
398
-- This is splitter; for recombination, look at 'recombineEithers'.
399
-- The sum of \"left\" and \"right\" lists should be equal to the
400
-- original list length, and the trail list should be the same length
401
-- as well. The entries in the resulting lists are reversed in
402
-- comparison with the original list.
403
splitEithers :: [Either a b] -> ([a], [b], [Bool])
404
splitEithers = foldl' splitter ([], [], [])
405
  where splitter (l, r, t) e =
406
          case e of
407
            Left  v -> (v:l, r, False:t)
408
            Right v -> (l, v:r, True:t)
409

    
410
-- | Recombines two \"left\" and \"right\" lists using a \"trail\"
411
-- list into a single 'Either' list.
412
--
413
-- This is the counterpart to 'splitEithers'. It does the opposite
414
-- transformation, and the output list will be the reverse of the
415
-- input lists. Since 'splitEithers' also reverses the lists, calling
416
-- these together will result in the original list.
417
--
418
-- Mismatches in the structure of the lists (e.g. inconsistent
419
-- lengths) are represented via 'Bad'; normally this function should
420
-- not fail, if lists are passed as generated by 'splitEithers'.
421
recombineEithers :: (Show a, Show b) =>
422
                    [a] -> [b] -> [Bool] -> Result [Either a b]
423
recombineEithers lefts rights trail =
424
  foldM recombiner ([], lefts, rights) trail >>= checker
425
    where checker (eithers, [], []) = Ok eithers
426
          checker (_, lefts', rights') =
427
            Bad $ "Inconsistent results after recombination, l'=" ++
428
                show lefts' ++ ", r'=" ++ show rights'
429
          recombiner (es, l:ls, rs) False = Ok (Left l:es,  ls, rs)
430
          recombiner (es, ls, r:rs) True  = Ok (Right r:es, ls, rs)
431
          recombiner (_,  ls, rs) t = Bad $ "Inconsistent trail log: l=" ++
432
                                      show ls ++ ", r=" ++ show rs ++ ",t=" ++
433
                                      show t