#!/opt/vmware/bin/python
#
# prototype process / java heap sizing script.
#
# See https://wiki.eng.vmware.com/CloudVM/Sizing for details
#
# Usage:
#   cloudvm-ram-size [-m availmem | -s size] service
#        The most common usage. If neither availmem nor size (one of tiny,
#        or large) are specified, we look up the total available
#        memory from the kernel. Prints out the amount of memory allocated to
#        the service if it's enabled, or 0 if not.
#
#   cloudvm-ram-size [-s size [-u]] [-e service,service...] -l
#        Prints out a listing of each service and how much memory it is to
#        to be allocated. -s is treated as above. If -u is not specified, we
#        look up the allocation sizes of all services, and re-apportion the
#        the memory not used by the disabled services to the rest, and print
#        out a list of enabled services and how much memory they need.
#
#        For the VISL case, where you want to find out how large a VM is needed
#        for a particular configuration (tiny,large...) for a particular
#        set of services, you should pass in '-u', which means do not try to
#        scale the sizes based on reapportioned memory (i.e. adding up all the
#        sizes gives you a recommended VM size (floor) for running those
#        services in that size configuration).
#
# In both cases, the service arguments can be either keys in AddDaemon(), or
# any of the firstboot script basenames for those keys.
#
# Eventually we should be reading the sizes from a config file
# and most likely the definitions of tiny/large... from a
# config file too.

import sys
import platform
import logging
import os
import collections
from optparse import OptionParser
import fileinput

isLinux = platform.system() == 'Linux'
isWindows = platform.system() == 'Windows'
SLES12 = isLinux and platform.dist()[1] == '12'

try:
   osIsPhoton = os.path.isfile('/etc/photon-release')
except AttributeError:
   osIsPhoton = False

sys.path.append(os.environ['VMWARE_PYTHON_PATH'])
from cis.utils import setupLogging, get_deployment_nodetype
from cis.defaults import (get_cis_log_dir, get_java_home, is_vmon_enabled,
                          get_component_home_dir)
import cis.svcscfg as svcscfg


# Dummy defaults added to avoid throwing exception in case this script
# is run during build time. Below env variables are guranteed to be present
# on cloudvm and ciswin.
VMWARE_CFG_DIR = os.environ.get('VMWARE_CFG_DIR', '/etc/vmware')
VMWARE_LOG_DIR = os.environ.get('VMWARE_LOG_DIR', '/var/log')

# cloudvm-ram-size filebased logging setup
logDir = os.path.join(get_cis_log_dir(), 'cloudvm')
cloudvmlogFile = os.path.join(logDir, 'cloudvm-ram-size.log')
setupLogging('cloudvm-ram-size', logMechanism='file', logDir=logDir,
             rotate_bytes=10 * 1024 * 1024, rotate_count=5)
logFormat = '%(asctime)-15s: %(message)s'

# values are determined by summing up individual service numbers
cloudVMRamSizeMapping = {
    'large': 0.0,
    'tiny': 0.0,
}

daemonSizes = {
    'tiny': {},
    'large': {},
}

serviceNames = {
}

jvmParameters = {
    'compressClassSpaceSize': {},
    'stacksize': {},
    'tinyGCThreads': {},
    'largeGCThreads': {},
}

firstbootScripts = {}
# look up service names by script
reverseServiceMap = collections.defaultdict(list)


class ServiceUtil(object):
    """
    Service related utils.
    """
    def __init__(self, serviceCfgFile=None):
        if serviceCfgFile is None:
            if isLinux:
                serviceCfgFile = \
                   '/usr/lib/vmware-visl-integration/config/services.json'
            else:
                serviceCfgFile = os.path.join(os.environ['VMWARE_CIS_HOME'],
                                              'visl-integration', 'config',
                                              'services.json')
        # Load service.json file.
        self._svcCfg = svcscfg.loadServicesFile(serviceCfgFile)
        self._deploymentNodeType = get_deployment_nodetype()

    def GetSvcInfo(self, serviceName):
        """
        Maps service names in service-layout.mfx to corresponding json nodes in
        services.json file.
        Given serviceName should be same as node name in services.json or
        in the format vmware-<node name in services.json>
        """
        sInfo = self._svcCfg.get(serviceName)
        if sInfo is None and serviceName.startswith('vmware-'):
            sInfo = self._svcCfg.get(serviceName.split('-', 1)[1])
        return sInfo

    def IsServiceActive(self, serviceName):
        """
        Checks if a given service is deployed on the current system.
        Defaults to True if the serviceName could not be found.
        """

        if serviceName == 'LinuxKernel' or serviceName == 'OS':
            # OS is not an active service, by 'OS' as service name
            # we list system memory.
            # Checking 'OS' in function GetSvcInfo generates false warning msg
            return True
        sInfo = self.GetSvcInfo(serviceName)
        if sInfo is None:
            # Cannot find the service, return True by default
            logging.warn('Service %s not found in services cfg. Assuming it'
                         ' is active on current system.' % serviceName)
            return True

        # Lookup service config to check if given service is deployed
        # on current system.
        return ('all' in sInfo['deploymentType'] or
                self._deploymentNodeType in sInfo['deploymentType'])


def GetRamMb():
    """
    Returns the amount of RAM this system has
    """
    if isLinux:
        with open('/proc/meminfo', 'r') as f:
            return int(f.readline().split()[1]) / 1024
    elif isWindows:
        import win32api
        return win32api.GlobalMemoryStatusEx()['TotalPhys'] / (1024 * 1024)
    else:
        # Unknown OS, assuming 8GB
        return 8192


def GetBuildType():
    """
    Returns 'beta', 'obj','release'...
    """
    # Default is 'release'
    bldType = 'release'
    try:
        if isLinux:
            with open('/etc/vmware/.buildInfo', 'r') as f:
                lines = f.readlines()
                for line in lines:
                    kvpair = line.split(':')
                    if len(kvpair) > 1 and kvpair[0] == 'VMBLD':
                        return kvpair[1].strip()
        elif isWindows:
            # TODO: Check windows build type
            return bldType
        return bldType
    except Exception:
        return bldType


def AddDaemon(script, cloudvm_name, ciswin_name, customMb, tinyMb, largeMb,
              compressClassSpaceSizeMb=0, stacksizeKB=0, tinyGCThreads=0,
              largeGCThreads=0):
    """
    customMb value overrides other values in calculation
    """
    if cloudvm_name == '-':
        cloudvm_name = 'OS'

    logging.debug('Adding daemon %s %s %s %s %s %s %s %s %s' %
                  (script, cloudvm_name,
                   customMb, tinyMb, largeMb, compressClassSpaceSizeMb,
                   stacksizeKB, tinyGCThreads, largeGCThreads))
    compressClassSpaceSize = int(compressClassSpaceSizeMb)
    # Incoming are heapsizes, add compressclasssize right now so rest of
    # calculations do not change.
    if int(customMb) == 0:
        daemonSizes['tiny'][cloudvm_name] = int(tinyMb) + \
                                            compressClassSpaceSize
        daemonSizes['large'][cloudvm_name] = int(largeMb) + \
                                            compressClassSpaceSize
    else:
        daemonSizes['tiny'][cloudvm_name] = int(customMb) + \
                                            compressClassSpaceSize
        daemonSizes['large'][cloudvm_name] = int(customMb) + \
                                             compressClassSpaceSize
    serviceNames[ciswin_name.lower()] = cloudvm_name
    cloudVMRamSizeMapping['tiny'] += daemonSizes['tiny'][cloudvm_name]
    cloudVMRamSizeMapping['large'] += daemonSizes['large'][cloudvm_name]

    jvmParameters['compressClassSpaceSize'][cloudvm_name] = \
        compressClassSpaceSize
    jvmParameters['stacksize'][cloudvm_name] = int(stacksizeKB)
    jvmParameters['tinyGCThreads'][cloudvm_name] = int(tinyGCThreads)
    jvmParameters['largeGCThreads'][cloudvm_name] = int(largeGCThreads)

    firstbootScripts[cloudvm_name] = script
    # Update reverseServiceMap. reverseServiceMap keeps track of list services
    # which correspond to a firstboot script.
    if reverseServiceMap.has_key(script):
        reverseServiceMap[script].append(cloudvm_name)
    else:
        reverseServiceMap[script] = [cloudvm_name]


def printJVMParameters(service, maxmemory, gcthreads=1, outputfile=None):
    jvmMaxHeap = maxmemory - jvmParameters['compressClassSpaceSize'][service]
    args = ['-Xmx%im' % jvmMaxHeap,
            '-XX:CompressedClassSpaceSize=%im' %
                jvmParameters['compressClassSpaceSize'][service],
            '-Xss%ik' % jvmParameters['stacksize'][service],
            '-XX:ParallelGCThreads=%i' % gcthreads]

    # PR 1083922 - Enable Asserts on non-release builds
    if GetBuildType() in ['obj', 'beta']:
        args.append('-ea')

    if outputfile is not None:
        with open(outputfile, 'wb') as fp:
            fp.write('\n'.join(args))
    else:
        sys.stdout.write('\n'.join(args))

def CgroupDaemonMemoryPath(daemon):
    '''
    Helper method to get path to cgroup memory details for a given daemon.
    It accepts following predefined keywords as daemon name.
    LinuxKernel : Returns root cgroup memory path.
    TOTAL or '' : Returns path to cloudvm cgroup group.
    '''
    if (SLES12 or osIsPhoton):
        cgroup_path = '/sys/fs/cgroup/memory'
    else:
        cgroup_path = '/sys/fs/cgroup'
    if daemon == 'LinuxKernel':
        cgroup_name = ''
    elif daemon == 'TOTAL' or daemon == '':
        if (SLES12 or osIsPhoton):
            cgroup_name = '/system.slice'
        else:
            cgroup_name = '/cloudvm'
    else:
        if (SLES12 or osIsPhoton):
            cgroup_name = ('/system.slice/%s.service') % daemon
        else:
            cgroup_name = ('/cloudvm/%s') % daemon
    return cgroup_path + cgroup_name

# ActualMaxRam gets the max_usage_in_bytes from cgroups in appliance
def ActualRam(daemon, metric):
    """
    Daemon's actual max memory from OS
    """
    if not isLinux:
        return 0

    if metric == 'max':
        memfile = '/memory.max_usage_in_bytes'
    elif metric == 'current':
        memfile = '/memory.usage_in_bytes'
    elif metric == 'limit':
        memfile = '/memory.limit_in_bytes'
    else:
        memfile = '/memory.stat'
    fullpathname = CgroupDaemonMemoryPath(daemon) + memfile
    try:
        with open(fullpathname, 'r') as f:
            lines = f.readlines()
            for line in lines:
                kvpair = line.split()
                if len(kvpair) > 1 and kvpair[0] == metric:
                    return int(kvpair[1])/1024/1024
                elif len(kvpair) == 1:
                    return int(kvpair[0])/1024/1024
            return -1
    except Exception:
        return -1


def SetMemoryLimit(daemon, allocatedMB, bufferMB):
    if not isLinux:
        return 0

    cgroup_path = CgroupDaemonMemoryPath(daemon)

    hardlimit = '/memory.limit_in_bytes'
    softlimit = '/memory.soft_limit_in_bytes'

    filename = [cgroup_path + softlimit,
                cgroup_path + hardlimit]
    limitBytes = (allocatedMB + bufferMB) * 1024 * 1024
    try:
        for i in range(2):
            os.system('echo ' + str(limitBytes) + ' > ' + filename[i])
        print ('Memory limit set for %s in %s = %d bytes(%d in MB)' %
               (daemon, cgroup_path, limitBytes,
                allocatedMB + bufferMB))
        logging.info('Setting memory limit for %s in cgroup %s = %d '
                     'bytes (%d in MB)' %
                     (daemon, cgroup_path, limitBytes, allocatedMB + bufferMB))
        return 0
    except Exception as e:
        print 'Error setting limit:', cgroup_path, e
        return -1


# ActualList gets the actual cgroups and daemons budgeted togther
def ActualList(k):
    """
    Actual list of daemons
    """
    if not isLinux:
        return k

    cgroup_path = CgroupDaemonMemoryPath('')

    daemon_list = ['LinuxKernel']
    for name in os.listdir(cgroup_path):
        if os.path.isdir('%s/%s' % (cgroup_path, name)):
            if (osIsPhoton or SLES12):
                daemon_list.append(name.replace('.service', ''))
            else:
                daemon_list.append(name)

    daemon_list.sort()
    return daemon_list


# AllocatedRam computes the memory requirement
# for a daemon by looking at the suggested sizes
# for the daemon in the nearest higher and lower
# 'standard' VM size, and extrapolating it to the
# actual available memory size ('systemRam').
def AllocatedRam(daemon, systemRam):
    """
    Each daemon gets allocated based on the slope of 'tiny' and 'large'
    compared to the sytemRam
    """
    if daemon not in daemonSizes['tiny']:
        return -1

    lowerVmMem = cloudVMRamSizeMapping['tiny']
    upperVmMem = cloudVMRamSizeMapping['large']
    lowerDaemonSize = daemonSizes['tiny'][daemon]
    upperDaemonSize = daemonSizes['large'][daemon]
    if systemRam <= lowerVmMem:
        return lowerDaemonSize
    else:
        ratio = (systemRam - lowerVmMem) / (upperVmMem - lowerVmMem)
        return int(lowerDaemonSize + ratio *
                   (upperDaemonSize - lowerDaemonSize))


def CalculateGCThreads(daemon, systemRam):
    """
    Each Java services gets GC Threads based on scaling
    """

    lowerVmMem = cloudVMRamSizeMapping['tiny']
    upperVmMem = cloudVMRamSizeMapping['large']
    lowerGCThreads = jvmParameters['tinyGCThreads'][daemon]
    higherGCThreads = jvmParameters['largeGCThreads'][daemon]
    if systemRam <= lowerVmMem:
        return lowerGCThreads
    else:
        ratio = (systemRam - lowerVmMem) / (upperVmMem - lowerVmMem)
        return int(lowerGCThreads + ratio * (higherGCThreads - lowerGCThreads))


# AddDaemons adds all the known daemons with some estimates of how much memory
# each will use at the various configuration sizes above.
def AddDaemons(daemonManifest):
    with open(daemonManifest, 'r') as f:
        for line in f:
            if line.startswith('#'):
                continue
            fields = line.split()
            if len(fields) < 6:
                logging.warn('Malformed line: "%s"' % line)
                # just skip malformed services
                continue
            AddDaemon(*fields)


# This function modifies service-layout.mfx and sets customMb
def SetCustomMb(daemonManifest, setCustom, changeDaemon):
    found = False
    for line in fileinput.input(daemonManifest, inplace=True):
        if line.startswith('#'):
            sys.stdout.write(line)
            continue
        fields = line.split()

        if ((isLinux and fields[1] == changeDaemon) or
            (isWindows and fields[2].lower() == changeDaemon.lower())):
            javafields = ''
            fields[3] = setCustom
            totfield = len(fields)
            if totfield > 6:
                javafields = ('%-17s %-11s %-13s %-14s' %
                              (str(fields[6]), str(fields[7]),
                               str(fields[8]), str(fields[9])))
            outline = '{0:<28} {1:<22} {2:<26} {3:<9} {4:<6} '\
                      '{5:<9} {6}\n'.format(
                str(fields[0]), str(fields[1]), str(fields[2]),
                str(fields[3]), str(fields[4]), str(fields[5]),
                javafields)
            sys.stdout.write(outline)
            found = True
        else:
            sys.stdout.write(line)
    if not found:
        logging.warn('Unknown service "%s"' % changeDaemon)
        raise ValueError('Unknown service "%s"' % changeDaemon)


# This is where we check for enabled services, and reallocate memory not
# consumed by the disabled services among the enabled services.
#
# The way we do this is:
#   - First, we compute a scale factor based on the ratio of total consumed
#     memory if all services were enabled, divided by the total memory of the
#     enabled services
#   - Then, we scale the sizes in the table above by this scale factor if the
#     service is enabled, or set it to 0 if disabled
#
# After this scaling, the AllocatedRam() routine worked as it did for the
# all-enabled case.
def CheckEnabledServices(serviceUtilObj, enabledServices=None,
                         unscaled=False):
    # First, construct a map of enabled services.
    serviceEnabled = {}
    serviceMap = daemonSizes['tiny']  # pick arbitrary one for checks
    if enabledServices is not None:
        # An explicit set of enabled services was passed in on the
        # command line. These can be either firstboot script names,
        # or service names (keys in AddDaemon() above).
        enabledSet = set(s.strip() for s in enabledServices.split(','))
        for s in enabledSet:
            if s in serviceMap:
                serviceEnabled[s] = True
            elif s in reverseServiceMap:
                serviceEnabled[reverseServiceMap[s]] = True
            else:
                print >>sys.stderr, 'Attempt to enable unknown service %s' % s
        # Fill in the remaining services as disabled for this usage
        for d in serviceMap:
            if d not in serviceEnabled:
                serviceEnabled[d] = False
    else:
        for service in daemonSizes['tiny']:
            serviceEnabled[service] = serviceUtilObj.IsServiceActive(service)

    # And now reset the daemonSizes based on the serviceEnabled flags
    # and reduce the value being resetted from the total
    # If the (-u) option is passed in, we do not update the total
    for size, svcMap in daemonSizes.iteritems():
        for d, _ in svcMap.iteritems():
            if not serviceEnabled[d]:
                if not unscaled:
                    cloudVMRamSizeMapping[size] -= daemonSizes[size][d]
                daemonSizes[size][d] = 0


def ParseOptions():
    usage = 'usage: %prog [-m availmem | -s size [-u]] [-l] '\
            '[ service | -e service,service...] ]'
    parser = OptionParser(usage)
    parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
                      help='print verbose explanations')
    parser.add_option('-m', '--memory', dest='sysmem', type='int',
                      help='available system memory', default=-1)
    parser.add_option('-s', '--size', dest='configsize', type='str',
                      help='configuration size', default=None)
    parser.add_option('-e', '--enabled', dest='enabledsvcs', type='str',
                      help='comma-separated list of enabled services',
                      default=None)
    parser.add_option('-l', '--list', dest='list', action='store_true',
                      help='list all services at given memory')
    parser.add_option('-c', '--checkManifest', dest='checkManifest',
                      help='Check manifest file on the total for each sizes')
    parser.add_option('-C', '--setCustom', dest='setCustom', metavar='NUMBER',
                      type='int', help='Set custom MB for named service ')
    parser.add_option('--setMemoryLimits', dest='setMemoryLimits',
                      metavar='NUMBER', type='int',
                      help='Set Memory Limits MB for each services '
                           'using <BufferSizeinMB>')
    parser.add_option('-S', '--status', dest='status', action='store_true',
                      help='list all services and its memory status with '
                           'their max and current usage')
    parser.add_option('-J', '--getJVMparameters', dest='getJVMparameters',
                      action='store_true',
                      help='Get JVM Option string for Java services related '
                           'to MaxHeap, CompressClassSize and Stacksize')
    parser.add_option('-u', '--unscaled', dest='unscaled', action='store_true',
                      help="don't scale enabled services to available memory")
    parser.add_option('-d', '--dump-services', dest='dumpsvcs',
                      action='store_true', help='dump service table')
    parser.add_option('-M', '--daemonManifest', dest='daemonManifest',
                      type='str', help='location of daemon manifest',
                      default=os.path.join(VMWARE_CFG_DIR,
                                           'service-layout.mfx'))
    parser.add_option('-D', '--service-setting', dest='servicesettings',
                      default=None, help='Path to services.json file.')
    parser.add_option('-O', '--output', dest='outputfile',
                      default=None, help='Output file.')

    (options, args) = parser.parse_args()
    if options.sysmem == -1:
        if options.configsize is not None:
            if options.configsize not in cloudVMRamSizeMapping:
                parser.error('Invalid argument to -s option')
            options.sysmem = cloudVMRamSizeMapping[options.configsize]
        else:
            # we do it this way to avoid even looking at /proc/meminfo
            # if we don't need to (e.g. testing on windows)
            options.sysmem = GetRamMb()
    else:
        if options.configsize is not None:
            parser.error('Cannot mix -m and -s options')
        if options.unscaled:
            parser.error('Cannot mix -u and -m options')

    if options.setCustom is not None:
        # cloudvm-ram-size cannot be negative or larger than system memory
        if options.setCustom < 0 or options.setCustom > options.sysmem:
            parser.error('Custom value %s is invalid, valid input range 0 - %s'
                         % (options.setCustom, options.sysmem))

    if options.setMemoryLimits is not None:
        # cloudvm-ram-size cannot be negative or larger than system memory
        if options.setMemoryLimits < 0 or \
                options.setMemoryLimits > options.sysmem:
            parser.error('Buffer of %sMB is invalid for memory limits,'
                         ' valid range 0 - %s where 0 is automatic'
                         % (options.setMemoryLimits, options.sysmem))
        if options.setMemoryLimits == 0:
            # automatic BufferSize should be 5% of RAM
            options.setMemoryLimits = int(options.sysmem * 0.05)

    if len(args) != 1 and not options.list and not options.status \
            and not options.dumpsvcs and not options.setMemoryLimits:
        parser.error('Must specify a single service name '
                     'if not listing services')
    return (options, args)


def DumpServices():
    print '%-25s %10s %10s' % ('Services', 'Tiny', 'Large')
    for d in daemonSizes['tiny'].keys():
        svcName = d
        print '%-25s %10d %10d' % (svcName, daemonSizes['tiny'][d],
                                   daemonSizes['large'][d])
    print '%-25s %10d %10d' % ('TOTAL', cloudVMRamSizeMapping['tiny'],
                               cloudVMRamSizeMapping['large'])


def main():
    # set up the daemon limits
    (options, args) = ParseOptions()
    systemRam = options.sysmem
    verbose = options.verbose

    serviceUtilObj = ServiceUtil(options.servicesettings)
    if options.setCustom is not None:
        SetCustomMb(options.daemonManifest, options.setCustom, args[0])
        sys.exit(0)

    AddDaemons(options.daemonManifest)
    if options.dumpsvcs:
        DumpServices()
        sys.exit(0)

    CheckEnabledServices(serviceUtilObj, options.enabledsvcs,
                         options.unscaled)

    # check out if service name valid
    serviceMap = daemonSizes['tiny']
    for index, arg in enumerate(args):
        if isWindows and arg.lower() in serviceNames:
            args[index] = serviceNames[arg.lower()]
        elif arg not in serviceMap:
            logging.warn('Unknown service "%s"' % arg)
            raise ValueError('Unknown service "%s"' % arg)

    # compute the OS set-aside
    osSize = AllocatedRam('LinuxKernel', systemRam)

    if not (options.list or options.status or options.setMemoryLimits):
        # Request for a specific daemon: print the value and quit.
        s = args[0]
        if s not in serviceMap:
            if s not in reverseServiceMap:
                logging.warn('Unknown service "%s"' % s)
                raise ValueError('Unknown service "%s"' % s)
            s = reverseServiceMap[s]
        allocated = AllocatedRam(s, systemRam)
        logging.info('Allocated %d MB out of %d MB to "%s"'
                     % (allocated, systemRam, s))
        if not options.getJVMparameters:
            print allocated
        else:
            gcthreads = CalculateGCThreads(s, systemRam)
            try:
                printJVMParameters(s, allocated, gcthreads, options.outputfile)
            except Exception as e:
                logging.error('Failed to get jvm params. Outfile %s, Err: %s' %
                              (options.outputfile, e))
                raise
        # rely on any exceptions to cause a non-zero
        # exit code -- which calling scripts should
        # check for and handle.
        sys.exit(0)

    # Else list memory requirements of all or specified
    # daemons or set cgroup memory limit
    if isWindows and options.setMemoryLimits:
        print 'Option not supported on Windows'
        sys.exit(1)
    if options.list or options.setMemoryLimits:
        daemons = set(args)
        listtotal = 0
        limittotal = 0
        for d in daemonSizes['tiny'].keys():
            if len(daemons) == 0 or d in daemons:
                r = AllocatedRam(d, systemRam)
                listtotal += r
                if r > 0:
                    if options.setMemoryLimits:
                        SetMemoryLimit(d, r, options.setMemoryLimits)
                        limittotal += r + options.setMemoryLimits
                    else:
                        print '%-20s = %8d' % (d, r)
        if len(daemons) == 0 and options.list:
            print '%-20s = %8d' % ('TOTAL(MB)', listtotal)
        elif options.setMemoryLimits:
            SetMemoryLimit('TOTAL', limittotal, 0)
    # Else show status
    if options.status:
        print 'Service-Name            AllocatedMB     MaxMB CurrentMB  '\
              'Curr-RSS    Cache  MapFiles   MemoryLimit'
        btot = 0
        rsstot = 0
        cachetot = 0
        mapftot = 0
        for d in ActualList(daemonSizes['tiny'].keys()):
            if d in daemonSizes['tiny'].keys():
                r = AllocatedRam(d, systemRam)
                btot += r
            else:
                r = -1
            m = ActualRam(d, 'max')
            c = ActualRam(d, 'current')
            rss = ActualRam(d, 'rss')
            cache = ActualRam(d, 'cache')
            mapf = ActualRam(d, 'mapped_file')
            limitf = ActualRam(d, 'limit')
            rsstot += rss
            cachetot += cache
            mapftot += mapf
            print '%-25s %9d %9d %9d %9d %8d %9d %13d' \
                  % (d, r, m, c, rss, cache, mapf, limitf)
        # In Cgroups LinuxKernel is separate in root so it needs
        # to be added to cloudvm group
        # For root cgroup max memory is always zero so use current
        m = ActualRam('TOTAL', 'max') + ActualRam('LinuxKernel', 'max')
        c = ActualRam('TOTAL', 'current') + ActualRam('LinuxKernel', 'current')
        limitf = ActualRam('TOTAL', 'limit')
        rss = rsstot + ActualRam('LinuxKernel', 'rss')
        cache = cachetot + ActualRam('LinuxKernel', 'cache')
        mapf = mapftot + ActualRam('LinuxKernel', 'mapped_file')
        print '%-25s %9d %9d %9d %9d %8d %9d %13d' \
              % ('TOTAL(RAM=' + str(systemRam) + 'MB)',
                 btot, m, c, rss, cache, mapf, limitf)


if __name__ == '__main__':
    main()
