On Wed, Dec 1, 2010 at 14:47, Iustin Pop <[email protected]> wrote:

> This improves on the previous change. Currently, the node and instance
> lists shipped around during data loading are (again) association lists.
> For instances it's not a big issue, but the node list is rewritten
> continuously while we assign instances to nodes, and that is very slow.
> The code was originally written for small (10-20 node) clusters, and
> today with multinodes… :)
>
> Rewriting to use Node.List/Instance.List makes a bit of a messy patch,
> but it allows to remove some custom functions for assoc. list
> processing, and also some custom unittests.
>
> At the end, the load time is almost halved, and we spend time now just
> in the string parsing code (which is, as we know, slow…).
> ---
>  Ganeti/HTools/Loader.hs |   37 ++++++++++++++-----------------------
>  Ganeti/HTools/Luxi.hs   |    4 ++--
>  Ganeti/HTools/QC.hs     |   45
> +++++++++++++++++++--------------------------
>  Ganeti/HTools/Rapi.hs   |    4 ++--
>  Ganeti/HTools/Simu.hs   |    9 +++++----
>  Ganeti/HTools/Text.hs   |    6 +++---
>  hscan.hs                |    2 +-
>  7 files changed, 46 insertions(+), 61 deletions(-)
>
> diff --git a/Ganeti/HTools/Loader.hs b/Ganeti/HTools/Loader.hs
> index e1f35a0..dcaef80 100644
> --- a/Ganeti/HTools/Loader.hs
> +++ b/Ganeti/HTools/Loader.hs
> @@ -37,9 +37,7 @@ module Ganeti.HTools.Loader
>     , Request(..)
>     ) where
>
> -import Data.Function (on)
>  import Data.List
> -import Data.Maybe (fromJust)
>  import qualified Data.Map as M
>  import Text.Printf (printf)
>
> @@ -93,35 +91,29 @@ lookupInstance kti inst =
>  -- | Given a list of elements (and their names), assign indices to them.
>  assignIndices :: (Element a) =>
>                  [(String, a)]
> -              -> (NameAssoc, [(Int, a)])
> +              -> (NameAssoc, Container.Container a)
>  assignIndices nodes =
>   let (na, idx_node) =
>           unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, setIdx v idx)))
>           . zip [0..] $ nodes
> -  in (M.fromList na, idx_node)
> -
> --- | Assoc element comparator
> -assocEqual :: (Eq a) => (a, b) -> (a, b) -> Bool
> -assocEqual = (==) `on` fst
> +  in (M.fromList na, Container.fromAssocList idx_node)
>
>  -- | For each instance, add its index to its primary and secondary nodes.
> -fixNodes :: [(Ndx, Node.Node)]
> +fixNodes :: Node.List
>          -> Instance.Instance
> -         -> [(Ndx, Node.Node)]
> +         -> Node.List
>  fixNodes accu inst =
>     let
>         pdx = Instance.pNode inst
>         sdx = Instance.sNode inst
> -        pold = fromJust $ lookup pdx accu
> +        pold = Container.find pdx accu
>         pnew = Node.setPri pold inst
> -        ac1 = deleteBy assocEqual (pdx, pold) accu
> -        ac2 = (pdx, pnew):ac1
> +        ac2 = Container.add pdx pnew accu
>     in
>       if sdx /= Node.noSecondary
> -      then let sold = fromJust $ lookup sdx accu
> +      then let sold = Container.find sdx accu
>                snew = Node.setSec sold inst
> -               ac3 = deleteBy assocEqual (sdx, sold) ac2
> -           in (sdx, snew):ac3
> +           in Container.add sdx snew ac2
>       else ac2
>
>  -- | Remove non-selected tags from the exclusion list
> @@ -168,11 +160,11 @@ commonSuffix nl il =
>  mergeData :: [(String, DynUtil)]  -- ^ Instance utilisation data
>           -> [String]             -- ^ Exclusion tags
>           -> [String]             -- ^ Untouchable instances
> -          -> (Node.AssocList, Instance.AssocList, [String])
> +          -> (Node.List, Instance.List, [String])
>           -- ^ Data from backends
>           -> Result (Node.List, Instance.List, [String])
> -mergeData um extags exinsts (nl, il, tags) =
> -  let il2 = Container.fromAssocList il
> +mergeData um extags exinsts (nl, il2, tags) =
> +  let il = Container.elems il2
>       il3 = foldl' (\im (name, n_util) ->
>                         case Container.findByName im name of
>                           Nothing -> im -- skipping unknown instance
> @@ -184,10 +176,9 @@ mergeData um extags exinsts (nl, il, tags) =
>       il4 = Container.map (filterExTags allextags .
>                            updateMovable exinsts) il3
>       nl2 = foldl' fixNodes nl (Container.elems il4)
> -      nl3 = Container.fromAssocList
> -            (map (\ (k, v) -> (k, Node.buildPeers v il4)) nl2)
> -      node_names = map (Node.name . snd) nl
> -      inst_names = map (Instance.name . snd) il
> +      nl3 = Container.map (\node -> Node.buildPeers node il4) nl2
> +      node_names = map Node.name (Container.elems nl)
> +      inst_names = map Instance.name il
>       common_suffix = longestDomain (node_names ++ inst_names)
>       snl = Container.map (computeAlias common_suffix) nl3
>       sil = Container.map (computeAlias common_suffix) il4
> diff --git a/Ganeti/HTools/Luxi.hs b/Ganeti/HTools/Luxi.hs
> index 24441b3..cc80908 100644
> --- a/Ganeti/HTools/Luxi.hs
> +++ b/Ganeti/HTools/Luxi.hs
> @@ -162,7 +162,7 @@ readData master =
>        )
>
>  parseData :: (Result JSValue, Result JSValue, Result JSValue)
> -          -> Result (Node.AssocList, Instance.AssocList, [String])
> +          -> Result (Node.List, Instance.List, [String])
>  parseData (nodes, instances, cinfo) = do
>   node_data <- nodes >>= getNodes
>   let (node_names, node_idx) = assignIndices node_data
> @@ -173,5 +173,5 @@ parseData (nodes, instances, cinfo) = do
>
>  -- | Top level function for data loading
>  loadData :: String -- ^ Unix socket to use as source
> -            -> IO (Result (Node.AssocList, Instance.AssocList, [String]))
> +            -> IO (Result (Node.List, Instance.List, [String]))
>  loadData master = readData master >>= return . parseData
> diff --git a/Ganeti/HTools/QC.hs b/Ganeti/HTools/QC.hs
> index a94b4a1..b4ab4b0 100644
> --- a/Ganeti/HTools/QC.hs
> +++ b/Ganeti/HTools/QC.hs
> @@ -110,7 +110,7 @@ makeSmallCluster node count =
>     let fn = Node.buildPeers node Container.empty
>         namelst = map (\n -> (Node.name n, n)) (replicate count fn)
>         (_, nlst) = Loader.assignIndices namelst
> -    in Container.fromAssocList nlst
> +    in nlst
>
>  -- | Checks if a node is "big" enough
>  isNodeBig :: Node.Node -> Int -> Bool
> @@ -448,11 +448,12 @@ prop_Text_Load_Instance name mem dsk vcpus status
> pnode snode pdx sdx =
>         ndx = if null snode
>               then [(pnode, pdx)]
>               else [(pnode, pdx), (snode, rsdx)]
> +        nl = Data.Map.fromList ndx
>         tags = ""
> -        inst = Text.loadInst ndx
> +        inst = Text.loadInst nl
>                [name, mem_s, dsk_s, vcpus_s, status, pnode, snode, tags]::
>                Maybe (String, Instance.Instance)
> -        fail1 = Text.loadInst ndx
> +        fail1 = Text.loadInst nl
>                [name, mem_s, dsk_s, vcpus_s, status, pnode, pnode, tags]::
>                Maybe (String, Instance.Instance)
>         _types = ( name::String, mem::Int, dsk::Int
> @@ -473,7 +474,8 @@ prop_Text_Load_Instance name mem dsk vcpus status pnode
> snode pdx sdx =
>              isNothing fail1)
>
>  prop_Text_Load_InstanceFail ktn fields =
> -    length fields /= 8 ==> isNothing $ Text.loadInst ktn fields
> +    length fields /= 8 ==> isNothing $ Text.loadInst nl fields
> +    where nl = Data.Map.fromList ktn
>
>  prop_Text_Load_Node name tm nm fm td fd tc fo =
>     let conv v = if v < 0
> @@ -822,35 +824,27 @@ testJobs =
>  -- | Loader tests
>
>  prop_Loader_lookupNode ktn inst node =
> -  isJust (Loader.lookupNode ktn inst node) == (node `elem` names)
> -    where names = map fst ktn
> +  Loader.lookupNode nl inst node == Data.Map.lookup node nl
> +  where nl = Data.Map.fromList ktn
>
>  prop_Loader_lookupInstance kti inst =
> -  isJust (Loader.lookupInstance kti inst) == (inst `elem` names)
> -    where names = map fst kti
> -
> -prop_Loader_lookupInstanceIdx kti inst =
> -  case (Loader.lookupInstance kti inst,
> -        findIndex (\p -> fst p == inst) kti) of
> -    (Nothing, Nothing) -> True
> -    (Just idx, Just ex) -> idx == snd (kti !! ex)
> -    _ -> False
> -
> -prop_Loader_assignIndices enames =
> -  length nassoc == length enames &&
> -  length kt == length enames &&
> -  (if not (null enames)
> -   then maximum (map fst kt) == length enames - 1
> +  Loader.lookupInstance il inst == Data.Map.lookup inst il
> +  where il = Data.Map.fromList kti
> +
> +prop_Loader_assignIndices nodes =
> +  Data.Map.size nassoc == length nodes &&
> +  Container.size kt == length nodes &&
> +  (if not (null nodes)
> +   then maximum (IntMap.keys kt) == length nodes - 1
>    else True)
> -  where (nassoc, kt) = Loader.assignIndices enames
> -        _types = enames::[(String, Node.Node)]
> +  where (nassoc, kt) = Loader.assignIndices (map (\n -> (Node.name n, n))
> nodes)
>
>
>  -- | Checks that the number of primary instances recorded on the nodes
>  -- is zero
>  prop_Loader_mergeData ns =
> -  let na = map (\n -> (Node.idx n, n)) ns
> -  in case Loader.mergeData [] [] [] (na, [], []) of
> +  let na = Container.fromAssocList $ map (\n -> (Node.idx n, n)) ns
> +  in case Loader.mergeData [] [] [] (na, Container.empty, []) of
>     Types.Bad _ -> False
>     Types.Ok (nl, il, _) ->
>       let nodes = Container.elems nl
> @@ -861,7 +855,6 @@ prop_Loader_mergeData ns =
>  testLoader =
>   [ run prop_Loader_lookupNode
>   , run prop_Loader_lookupInstance
> -  , run prop_Loader_lookupInstanceIdx
>   , run prop_Loader_assignIndices
>   , run prop_Loader_mergeData
>   ]
> diff --git a/Ganeti/HTools/Rapi.hs b/Ganeti/HTools/Rapi.hs
> index 37ac578..58df9bd 100644
> --- a/Ganeti/HTools/Rapi.hs
> +++ b/Ganeti/HTools/Rapi.hs
> @@ -131,7 +131,7 @@ readData master = do
>
>  -- | Builds the cluster data from the raw Rapi content
>  parseData :: (Result String, Result String, Result String)
> -          -> Result (Node.AssocList, Instance.AssocList, [String])
> +          -> Result (Node.List, Instance.List, [String])
>  parseData (node_body, inst_body, tags_body) = do
>   node_data <- node_body >>= getNodes
>   let (node_names, node_idx) = assignIndices node_data
> @@ -142,5 +142,5 @@ parseData (node_body, inst_body, tags_body) = do
>
>  -- | Top level function for data loading
>  loadData :: String -- ^ Cluster or URL to use as source
> -            -> IO (Result (Node.AssocList, Instance.AssocList, [String]))
> +            -> IO (Result (Node.List, Instance.List, [String]))
>  loadData master = readData master >>= return . parseData
> diff --git a/Ganeti/HTools/Simu.hs b/Ganeti/HTools/Simu.hs
> index c29f3f5..47618b9 100644
> --- a/Ganeti/HTools/Simu.hs
> +++ b/Ganeti/HTools/Simu.hs
> @@ -6,7 +6,7 @@ This module holds the code for parsing a cluster
> description.
>
>  {-
>
> -Copyright (C) 2009 Google Inc.
> +Copyright (C) 2009, 2010 Google Inc.
>
>  This program is free software; you can redistribute it and/or modify
>  it under the terms of the GNU General Public License as published by
> @@ -35,6 +35,7 @@ import Text.Printf (printf)
>
>  import Ganeti.HTools.Utils
>  import Ganeti.HTools.Types
> +import qualified Ganeti.HTools.Container as Container
>  import qualified Ganeti.HTools.Node as Node
>  import qualified Ganeti.HTools.Instance as Instance
>
> @@ -52,7 +53,7 @@ parseDesc desc =
>
>  -- | Builds the cluster data from node\/instance files.
>  parseData :: String -- ^ Cluster description in text format
> -         -> Result (Node.AssocList, Instance.AssocList, [String])
> +         -> Result (Node.List, Instance.List, [String])
>  parseData ndata = do
>   (cnt, disk, mem, cpu) <- parseDesc ndata
>   let nodes = map (\idx ->
> @@ -62,10 +63,10 @@ parseData ndata = do
>                             (fromIntegral cpu) False defaultGroupID
>                     in (idx, Node.setIdx n idx)
>                   ) [1..cnt]
> -  return (nodes, [], [])
> +  return (Container.fromAssocList nodes, Container.empty, [])
>
>  -- | Builds the cluster data from node\/instance files.
>  loadData :: String -- ^ Cluster description in text format
> -         -> IO (Result (Node.AssocList, Instance.AssocList, [String]))
> +         -> IO (Result (Node.List, Instance.List, [String]))
>  loadData = -- IO monad, just for consistency with the other loaders
>   return . parseData
> diff --git a/Ganeti/HTools/Text.hs b/Ganeti/HTools/Text.hs
> index 242620e..b4f9b72 100644
> --- a/Ganeti/HTools/Text.hs
> +++ b/Ganeti/HTools/Text.hs
> @@ -135,7 +135,7 @@ loadInst _ s = fail $ "Invalid/incomplete instance
> data: '" ++ show s ++ "'"
>  -- a supplied conversion function.
>  loadTabular :: (Monad m, Element a) =>
>                [String] -> ([String] -> m (String, a))
> -            -> m (NameAssoc, [(Int, a)])
> +            -> m (NameAssoc, Container.Container a)
>  loadTabular lines_data convert_fn = do
>   let rows = map (sepSplit '|') lines_data
>   kerows <- mapM convert_fn rows
> @@ -148,7 +148,7 @@ readData = readFile
>
>  -- | Builds the cluster data from text input.
>  parseData :: String -- ^ Text data
> -          -> Result (Node.AssocList, Instance.AssocList, [String])
> +          -> Result (Node.List, Instance.List, [String])
>  parseData fdata = do
>   let flines = lines fdata
>       (nlines, ilines) = break null flines
> @@ -163,5 +163,5 @@ parseData fdata = do
>
>  -- | Top level function for data loading
>  loadData :: String -- ^ Path to the text file
> -         -> IO (Result (Node.AssocList, Instance.AssocList, [String]))
> +         -> IO (Result (Node.List, Instance.List, [String]))
>  loadData afile = readData afile >>= return . parseData
> diff --git a/hscan.hs b/hscan.hs
> index 06def66..ad503f6 100644
> --- a/hscan.hs
> +++ b/hscan.hs
> @@ -89,7 +89,7 @@ fixSlash = map (\x -> if x == '/' then '_' else x)
>
>
>  -- | Generates serialized data from loader input
> -processData :: Result (Node.AssocList, Instance.AssocList, [String])
> +processData :: Result (Node.List, Instance.List, [String])
>             -> Result (Node.List, Instance.List, String)
>  processData input_data = do
>   (nl, il, _) <- input_data >>= Loader.mergeData [] [] []
> --
> 1.7.2.3
>
>
LGTM

Reply via email to