| {-| xentop CPU data collector |
| |
| -} |
| |
| {- |
| |
| Copyright (C) 2015 Google Inc. |
| All rights reserved. |
| |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions are |
| met: |
| |
| 1. Redistributions of source code must retain the above copyright notice, |
| this list of conditions and the following disclaimer. |
| |
| 2. Redistributions in binary form must reproduce the above copyright |
| notice, this list of conditions and the following disclaimer in the |
| documentation and/or other materials provided with the distribution. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
| IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
| CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| -} |
| |
| module Ganeti.DataCollectors.XenCpuLoad |
| ( dcName |
| , dcVersion |
| , dcFormatVersion |
| , dcCategory |
| , dcKind |
| , dcReport |
| , dcUpdate |
| ) where |
| |
| import Prelude () |
| import Ganeti.Prelude |
| |
| import Control.Applicative (liftA2) |
| import Control.Arrow ((***)) |
| import Control.Monad (liftM, when) |
| import Control.Monad.IO.Class (liftIO) |
| import qualified Data.Map as Map |
| import Data.Maybe (mapMaybe) |
| import qualified Data.Sequence as Seq |
| import System.Process (readProcess) |
| import qualified Text.JSON as J |
| import System.Time (ClockTime, getClockTime, addToClockTime) |
| |
| import Ganeti.BasicTypes (GenericResult(..), Result, genericResult, runResultT) |
| import qualified Ganeti.Constants as C |
| import Ganeti.DataCollectors.Types |
| import Ganeti.Utils (readMaybe, clockTimeToUSec, diffClockTimes) |
| |
| -- | The name of this data collector. |
| dcName :: String |
| dcName = C.dataCollectorXenCpuLoad |
| |
| -- | The version of this data collector. |
| dcVersion :: DCVersion |
| dcVersion = DCVerBuiltin |
| |
| -- | The version number for the data format of this data collector. |
| dcFormatVersion :: Int |
| dcFormatVersion = 1 |
| |
| -- | The category of this data collector. |
| dcCategory :: Maybe DCCategory |
| dcCategory = Nothing |
| |
| -- | The kind of this data collector. |
| dcKind :: DCKind |
| dcKind = DCKPerf |
| |
| -- | Read xentop output, if this program is available. |
| readXentop :: IO (Result String) |
| readXentop = |
| runResultT . liftIO $ readProcess C.xentopCommand ["-f", "-b", "-i", "1"] "" |
| |
| -- | Parse output of xentop command. |
| parseXentop :: String -> Result (Map.Map String Double) |
| parseXentop s = do |
| let values = map words $ lines s |
| case values of |
| [] -> Bad "No output received" |
| (name_header:_:cpu_header:_):vals -> do |
| when (name_header /= "NAME" || cpu_header /= "CPU(sec)") |
| $ Bad "Unexpected data format" |
| return . Map.fromList |
| $ mapMaybe |
| (\ dom -> case dom of |
| name:_:cpu:_ -> if name /= "Domain-0" |
| then liftM ((,) name) $ readMaybe cpu |
| else Nothing |
| _ -> Nothing |
| ) |
| vals |
| _ -> Bad "Insufficient number of output columns" |
| |
| |
| -- | Add a new value to a sequence of observations, taking into account |
| -- counter rollovers. In case of a rollover, we drop the joining interval |
| -- so that we do not have to make assumptions about the value at which is |
| -- rolled over, but we do keep the right sequence, appropriately moved. |
| combineWithRollover :: Seq.Seq (ClockTime, Double) |
| -> Seq.Seq (ClockTime, Double) |
| -> Seq.Seq (ClockTime, Double) |
| combineWithRollover new old | Seq.null new || Seq.null old = new Seq.>< old |
| combineWithRollover new old = |
| let (t2, x2) = Seq.index new $ Seq.length new - 1 |
| (t1, x1) = Seq.index old 0 |
| in if x2 >= x1 |
| then new Seq.>< old |
| else let delta_t = diffClockTimes t2 t1 |
| deltax = x2 - x1 |
| old' = (addToClockTime delta_t *** (+ deltax)) |
| <$> Seq.drop 1 old |
| in new Seq.>< old' |
| |
| -- | Updates the given Collector data. |
| dcUpdate :: Maybe CollectorData -> IO CollectorData |
| dcUpdate maybeCollector = do |
| let oldData = case maybeCollector of |
| Just (InstanceCpuLoad x) -> x |
| _ -> Map.empty |
| now <- getClockTime |
| newResult <- liftM (>>= parseXentop) readXentop |
| let newValues = Map.map (Seq.singleton . (,) now) |
| $ genericResult (const Map.empty) id newResult |
| sampleSizeUSec = fromIntegral C.cpuavgloadWindowSize * 1000000 |
| combinedValues = Map.unionWith combineWithRollover newValues oldData |
| withinRange = Map.map |
| (Seq.dropWhileR |
| ((<) sampleSizeUSec |
| . (clockTimeToUSec now -) |
| . clockTimeToUSec . fst)) |
| combinedValues |
| withoutOld = Map.filter |
| (liftA2 (&&) (not . Seq.null) |
| $ (>) (fromIntegral |
| $ 3 * C.xentopAverageThreshold * 1000000) |
| . (clockTimeToUSec now -) . clockTimeToUSec |
| . fst . flip Seq.index 0) |
| withinRange |
| return $ InstanceCpuLoad withoutOld |
| |
| -- | From a list of timestamps and cumulative CPU data, compute the |
| -- average CPU activity in vCPUs. |
| loadAverage :: Seq.Seq (ClockTime, Double) -> Maybe Double |
| loadAverage observations = do |
| when (Seq.null observations) Nothing |
| let (t2, cpu2) = Seq.index observations 0 |
| (t1, cpu1) = Seq.index observations $ Seq.length observations - 1 |
| tUsec2 = clockTimeToUSec t2 |
| tUsec1 = clockTimeToUSec t1 |
| when (tUsec2 - tUsec1 < (fromIntegral C.xentopAverageThreshold * 1000000)) |
| Nothing |
| return $ 1000000 * (cpu2 - cpu1) / fromIntegral (tUsec2 - tUsec1) |
| |
| -- | The data exported by the data collector, taken from the default location. |
| dcReport :: Maybe CollectorData -> IO DCReport |
| dcReport maybeCollector = |
| let collectedData = case maybeCollector of |
| Just (InstanceCpuLoad x) -> x |
| _ -> Map.empty |
| loads = Map.mapMaybe loadAverage collectedData |
| in buildReport dcName dcVersion dcFormatVersion dcCategory dcKind |
| . J.JSObject . J.toJSObject . Map.toAscList $ Map.map J.showJSON loads |