#!/usr/bin/env python
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
# 
#   http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

"""
hawq checkperf -- Check the hardware for Greenplum Database

Usage: hawq checkperf <options>

    --version  : print version information
    -d segdir  : where segdir is a directory on which to test I/O performance. 
                 Multiple -d flags may be specified for multiple directories
                 on each host. These will typically be your Greenplum primary
                 (and/or mirror) data directories. 
                 e.g. -d /dbfast1 -d /dbfast2
    -?         : print this help text
    -v         : enable verbose
    -V         : enable very verbose
    -D         : print per host disk stats on disk read/write tests
    -r flags   : where flags specifies the tests to run [default=dsn]. 
                 Supported test flags are:
                       d: runs only disk tests
                       s: runs only stream benchmark
                       n: runs only netperf tests (serial)
                       N: runs only netperf tests (parallel)
                       M: runs only netperf tests (full matrix)
    -B size    : where size specifies the block size for disk performance tests
                 [default=32KB] e.g. 1KB, 4MB
    -S size    : where size specifies the file size for disk performance tests
                 [default=2X Memory] e.g. 500MB, 16GB
    -h host    : the host to connect to (multiple -h is okay)
    -f file    : a file listing all hosts to connect to
    --duration : how long to run network test (default 5 seconds)
    --netperf  : use netperf instead of gpnetbenchServer/gpnetbenchClient
"""

import os, sys

sys.path.append(sys.path[0] + '/lib')
try:
    import getopt, math, StringIO, stat, subprocess, signal
    from gppylib.gpparseopts import OptParser
    from gppylib.util import ssh_utils
    from gppylib.gpcoverage import GpCoverage
except ImportError, e:
    sys.exit('Error: unable to import module: ' + str(e))


GPHOME=os.getenv('GPHOME')
if GPHOME is None:
    sys.exit('Please set GPHOME environment variable')

USER=os.getenv('USER')
if USER is None or USER is ' ':
    sys.exit('Please set USER environment variable')

################
def usage(exitarg):
    parser = OptParser()
    try:
        parser.print_help()
    except:
        print __doc__
    sys.exit(exitarg)


class Global():
    script_name = os.path.split(__file__)[-1]
    opt = {}
    opt['-d'] = []
    opt['-D'] = False
    opt['-v'] = False
    opt['-V'] = False
    opt['-r'] = ''
    opt['-B'] = 1024 * 32
    opt['-S'] = 0
    opt['-h'] = []
    opt['-f'] = None
    opt['--duration'] = 15  # default to 15s
    opt['--net'] = None
    opt['--netserver'] = 'gpnetbenchServer'
    opt['--netclient'] = 'gpnetbenchClient'

GV = Global()

#############
def killall(procname):
    return 'F=%s && (pkill $F || pkill -f $F || killall -9 $F) > /dev/null 2>&1 || true' % procname

###############################
def strcmd(cmd):
    return reduce(lambda x, y: x + ' ' + y, map(lambda x: x.find(' ') > 0 and "'" + x + "'" or x, cmd))

#############
def gpssh(cmd):
    c = ['%s/bin/gpssh' % GPHOME]
    if (GV.opt['-V']): c.append('-v')
    if (GV.opt['-f']):
        c.append('-f')
        c.append(GV.opt['-f'])
    else:
        for h in GV.opt['-h']: 
            c.append('-h')
            c.append(h)
    c.append(cmd)

    if GV.opt['-v']: print '[Info]', strcmd(c)
    p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    out = p.stdout.read(-1)
    rc = p.wait()
    return (not rc, out)

#############
def gpscp(src, dst):
    c = ['%s/bin/gpscp' % GPHOME]
    if (GV.opt['-V']): c.append('-v')
    if (GV.opt['-f']):
        c.append('-f')
        c.append(GV.opt['-f'])
    else:
	for h in GV.opt['-h']: 
            c.append('-h')
            c.append(h)
    c.append(src)
    c.append(dst)
    if GV.opt['-v']: print '[Info]', strcmd(c)
    p = subprocess.Popen(c, stdout=subprocess.PIPE)
    out = p.stdout.read(-1)
    rc = p.wait()
    return (not rc, out)

################
def getPlatform():
    if sys.platform.find('linux') >= 0: return 'linux'
    if sys.platform.find('darwin') >= 0: return 'darwin'
    if sys.platform.find('sunos5') >= 0: return 'sunos5'
    return '?'

################
def getMemory():
    if getPlatform() == 'linux':
        ok, out = run("sh -c 'cat /proc/meminfo | grep MemTotal'")
        if not ok: return 0
        list = out.strip().split(' ')
        val = int(list[len(list) - 2])
        factor = list[len(list) - 1]
        if factor == 'kB': 
            return val * 1024
        return 0

    if getPlatform() == 'darwin':
        ok, out = run("/usr/sbin/sysctl hw.physmem")
        if not ok: return 0
        list = out.strip().split(' ')
        val = int(list[1])
        return val

    if getPlatform() == 'sunos5':
        ok, out = run("sh -c \"/usr/sbin/prtconf | awk '/^Memory/{print}'\"")
        if not ok: return 0
        list = out.strip().split(' ')
        val = int(list[2])
        factor = list[3]
        if factor == 'Megabytes': 
            return val * 1024 * 1024
        return 0

    return 0

################
def parseMemorySize(line):
    factor = 1
    try:
	line = line.strip().upper()
	if line.endswith('B'): line = line[:-1]
	if line.endswith('G'): factor = 1024 * 1024 * 1024
	elif line.endswith('M'): factor = 1024 * 1024
	elif line.endswith('K'): factor = 1024
	if factor > 1: line = line[:-1]
	return int(line) * factor
    except Exception, err: 
        print 'Error: ' + err
        sys.exit(-1)
        return 0

#############
def parseDuration(val):
    factor = 1
    try:
        val = val.strip().upper()
        if val.endswith('S'): val = val[:-1]
        if val.endswith('M'): factor = 60
        if val.endswith('H'): factor = 3600
        if val.endswith('D'): factor = 86400
        if factor > 1: val = val[:-1]
        return int(val) * factor
    except Exception, err:
        print "exception: %s" % err
        sys.exit(-1)


#############
def print_version():
    print '%s version $Revision$' % GV.script_name
    sys.exit(0)

#############
def parseCommandLine():
    global opt
    try:
	(options, args) = getopt.getopt(sys.argv[1:], '?vVDd:r:B:S:p:h:f:', ['duration=', 'version', 'netperf'])
    except Exception, e:
	usage('Error: ' + str(e))

    for (switch, val) in options:
        if (switch == '-?'): usage(0)
        elif (switch[1] in 'vVD'): GV.opt[switch] = True
        elif (switch[1] in 'dh'): GV.opt[switch].append(val)
        elif (switch[1] in 'rf'): GV.opt[switch] = val
        elif (switch[1] in 'BS'): GV.opt[switch] = parseMemorySize(val)
        elif (switch == '--duration'): GV.opt[switch] = parseDuration(val)
        elif (switch == '--version'): print_version()
        elif (switch == '--netperf'): 
            GV.opt['--netserver'] = 'netserver'
            GV.opt['--netclient'] = 'netperf'

    # run default tests (if not specified)
    if GV.opt['-r'] == '':
        GV.opt['-r'] = 'dsN'

    # parse netperf test type, error if more than one specified
    netcount = 0
    if GV.opt['-r'].find('n') >= 0: 
        GV.opt['--net'] = 'netperf'
        netcount += 1
    if GV.opt['-r'].find('N') >= 0:
        GV.opt['--net'] = 'parallel'
        netcount += 1
    if GV.opt['-r'].find('M') >= 0:
        GV.opt['--net'] = 'matrix'
        netcount += 1
    if netcount > 1:
        usage('Error: please only specify one network test at a time (-r [n|M|N])')
        
    if GV.opt['-V']: GV.opt['-v'] = True

    if (len(GV.opt['-d']) == 0): 
	usage('Error: please specify at least one segdir (-d switch)')

    if not (0 <= GV.opt['-B'] and 0 <= GV.opt['-S']):
	usage('Error: please enter valid memory sizes for -B or -S switches')

    hf = (len(GV.opt['-h']) and 1 or 0) + (GV.opt['-f'] and 1 or 0)
    if hf != 1:
	usage('Error: please specify at least one of -h or -f args, but not both')

    if (GV.opt['-B']) > 1024*1024:
	usage('Error: maximum size for -B parameter is 1MB')

    if GV.opt['-S'] == 0:
        GV.opt['-S'] = 2 * getMemory() / len(GV.opt['-d'])
    else:
        GV.opt['-S'] /= len(GV.opt['-d'])

    if GV.opt['--duration'] <= 0:
        GV.opt['--duration'] = 15
        print '[INFO] Invalid network duration specified.  Using default (15 seconds)'

    # strip the last '/' from the dir
    dd = []
    for d in GV.opt['-d']:
	if d and d[-1] == '/': d = d[:-1]
	dd.append(d)
    GV.opt['-d'] = dd

    def strcmd(cmd):
        return reduce(lambda x, y: x + ' ' + y, map(lambda x: x.find(' ') > 0 and "'" + x + "'" or x, cmd))

    print strcmd(sys.argv)


#############
def run(cmd):
    f = None
    ok = False
    out = ''
    try:
	try:
	    if GV.opt['-v']: print '[Info]', cmd
	    f = os.popen(cmd)
	    out = f.read()
	    ok = not f.close()
	    f = None
	except KeyboardInterrupt:
	    raise
    finally:
	if f: f.close()

    if GV.opt['-v'] and out and out != '': print out

    if out and out.find('Permission denied') >= 0 and out.find('publickey') >= 0 : 
	if not GV.opt['-v']: print out
	print 'Please use gpssh-exkeys to exchange keys ...'
	sys.exit(1)

    return (ok, out)

#############
runTeardown_torndown = False
def runTeardown():
    global runTeardown_torndown
    if runTeardown_torndown: return
    if GV.opt['-v']:
	print '--------------------'
	print '  TEARDOWN'
	print '--------------------'
    dirs = ''
    for d in GV.opt['-d']: dirs = '%s %s/gpcheckperf_$USER' % (dirs, d)
    try:
        (ok, out) = gpssh('rm -rf ' + dirs)
    except:
	pass

    try:
        if GV.opt['--net']:
            (ok, out) = gpssh(killall(GV.opt['--netserver']))
    except:
        pass

    runTeardown_torndown = True

#############
def runSetup():
    if GV.opt['-v']:
	print '--------------------'
	print '  SETUP'
	print '--------------------'
    okCount = 0
    try:
        #check hostfile if have -f opt
        if GV.opt['-f']:
            if not os.path.exists(GV.opt['-f']):
                sys.exit("[Error] unable to find hostfile: %s" % GV.opt['-f'])
	#check python reachable
	if GV.opt['-v']: print '[Info] verify python interpreter exists'
	(ok, out) = gpssh('python -c print')
	if not ok:
	    if not GV.opt['-v']: print out
	    sys.exit("[Error] unable to find python interpreter on some hosts\n"
		     +"        verify PATH variables on the hosts")

	#mkdir cperf
	if GV.opt['-v']: print '[Info] making hawq checkperf directory on all hosts ... '
	dirs = ''
	for d in GV.opt['-d']: dirs = '%s %s/gpcheckperf_$USER' % (dirs, d)

        cmd = 'rm -rf %s ; mkdir -p %s' % (dirs, dirs)
	(ok, out) = gpssh(cmd)
	if not ok: 
	    sys.exit("[Error] unable to make hawq checkperf directory. \n"
		   + "        command failed: " + cmd)

    except:
	runTeardown()
	raise

################
def copyExecOver(fname):

    #copy to this dir
    d = GV.opt['-d'][0]
    target = '%s/gpcheckperf_$USER/%s' % (d, fname)
    path = '%s/lib/%s' % (sys.path[0], fname)

    if not os.path.isfile(path):
        print "binary file not found: %s" % path
        sys.exit(1)

    if GV.opt['-v']: print '[Info] copy local %s to remote %s' % (path, target)

    try: pathStat = os.stat(path)
    except: sys.exit('[Error] cannot stat file ' + path)
    if not stat.S_ISREG(pathStat.st_mode): sys.exit('[Error] invalid file ' + path)
    if not os.access(path, os.X_OK): sys.exit('[Exit] file not executable: ' + path)

    (ok, out) = gpscp(path, '=:%s' % target)
    if not ok: sys.exit('[Error] command failed: gpscp %s =:%s' % (path, target))
    
    #chmod +x file
    (ok, out) = gpssh('chmod a+rx %s' % target)
    if not ok: sys.exit('[Error] command failed: chmod a+rx %s' % target)
    
    return target


#############
def parseMultiDDResult(out):
    # parse output of "time -p"
    out = StringIO.StringIO(out)
    result = {}
    bytes = 0
    for line in out:
	i = line.find(']')
	if i == -1: continue
	o = line[i+2:]

	if o.startswith('multidd total bytes '):
	    h = line[1:i]
	    o = o.split()
	    bytes = int(o[-1])
	    continue
	    
	if o.startswith('real'):
	    h = line[1:i]
	    o = o.split()
	    time = float(o[1])
	    if time < 0.1: time = 0.1 		# avoid division by zero
	    mbps = bytes / time / 1024 / 1024
	    result[h] = (mbps, time, bytes)
	    continue

    return result



#############
def runDiskWriteTest(multidd):
    print
    print '--------------------'
    print '--  DISK WRITE TEST'
    print '--------------------'
    
    cmd = 'time -p ' + multidd
    for d in GV.opt['-d']:
	cmd = cmd + (' -i /dev/zero -o %s/gpcheckperf_$USER/ddfile' % d)
    if GV.opt['-B']: cmd = cmd + (' -B %d' % GV.opt['-B'])
    if GV.opt['-S']: cmd = cmd + (' -S %d' % GV.opt['-S'])
    (ok, out) = gpssh(cmd)
    if not ok:
	sys.exit('[Error] command failed: ' + cmd)
    return parseMultiDDResult(out)


#############
def runDiskReadTest(multidd):
    print
    print '--------------------'
    print '--  DISK READ TEST'
    print '--------------------'

    cmd = 'time -p ' + multidd
    for d in GV.opt['-d']:
	cmd = cmd + (' -o /dev/null -i %s/gpcheckperf_$USER/ddfile' % d)
    if GV.opt['-B']: cmd = cmd + (' -B %d' % GV.opt['-B'])
    if GV.opt['-S']: cmd = cmd + (' -S %d' % GV.opt['-S'])
    (ok, out) = gpssh(cmd)
    if not ok:
	sys.exit('[Error] command failed: ' + cmd)
    return parseMultiDDResult(out)


#############
def runStreamTest():
    print
    print '--------------------'
    print '--  STREAM TEST'
    print '--------------------'

    rmtPath = copyExecOver('stream')
    (ok, out) = gpssh(rmtPath)
    if not ok: sys.exit('[Error] command failed: ' + rmtPath)
    out = StringIO.StringIO(out)
    result = {}
    for line in out:
	i = line.find(']')
	o = line[i+2:]
	if o.startswith('Copy:'): 
	    h = line[1:i]
	    o = o.split()
	    result[h] = (float(o[1]), 0, 0)
    return result


#############
def startNetServer():
    port = 23000
    rmtPath = copyExecOver(GV.opt['--netserver'])

    for i in xrange(5):
        if i > 0: print '[Warning] retrying with port %d' % port
        (ok, out) = gpssh(killall(GV.opt['--netserver']))

        (ok, out) = gpssh('%s -p %d > /dev/null 2>&1' % (rmtPath, port))
        if ok: return port

        if GV.opt['-v']: print '[Warning] start netserver with port %d failed' % port
        port += 12

    return 0

#############
def killProc(proc):
    if proc: 
        try:
            os.kill(proc.pid, signal.SIGKILL)
        except OSError:
            pass
        return proc.wait()
    return 0


#############
def spawnNetperfTestBetween(x, y, netperf_path, netserver_port, sec=5):
    cmd = ('%s -H %s -p %d -t TCP_STREAM -l %s -f M -P 0 '
           % (netperf_path, y, netserver_port, sec))
    c = ['ssh', '-o', 'BatchMode yes', 
         '-o', 'StrictHostKeyChecking no',
         x, cmd]
    proc = None
    try: 
        if GV.opt['-v']: print '[Info]', strcmd(c)
        proc = subprocess.Popen(c, stdout=subprocess.PIPE)
    except KeyboardInterrupt:
        killProc(proc)
        raise
    if not proc:
        print '[Warning] netperf failed on %s -> %s' % (x, y)
        return (None, None, None)

    return (proc, x, y)


#############
def reapNetperfTest(proc, x, y):
    ok = True
    out = None
    if proc:
        try:
            rc = proc.wait()
            out = proc.stdout.read(-1)
            ok = not killProc(proc)
            proc = None
        except KeyboardInterrupt, ki:
            raise
        except:
            pass
        finally:
            proc = None
    
    if GV.opt['-v'] and out and out != '': 
        print '[Info]',out
    if out and out.find('Permission denied') >= 0 and out.find('publickey') >= 0 : 
        if not GV.opt['-v']: 
            print out
        print '[Error] Please use gpssh-exkeys to exchange keys ...'
        sys.exit(1)

    if not ok: 
        print '[Warning] netperf failed on %s -> %s' % (x, y)
        return []

    for line in StringIO.StringIO(out):
        line = line.split()
        if len(line) != 5: continue
	l = [x, y]
	l.extend(map(lambda x: float(x), line))
        if GV.opt['-v']: 
            print '[Info] %s -> %s : %s' % (x, y, str(line))
	return l

    print '[Warning] netperf failed on %s -> %s (invalid result)' % (x, y)
    print '[Info]', out
    return []



#############
def runNetperfTestBetween(x, y, netperf_path, netserver_port):
    (proc, x, y) = spawnNetperfTestBetween(x, y, netperf_path, netserver_port, GV.opt['--duration'])
    if not proc: 
        return []

    return reapNetperfTest(proc, x, y)


#############
def setupNetPerfTest():
    print
    print '-------------------'
    print '--  NETPERF TEST'
    print '-------------------'
    
    hostlist = ssh_utils.HostList()
    for h in GV.opt['-h']: hostlist.add(h)
    if GV.opt['-f']: hostlist.parseFile(GV.opt['-f'])

    h = hostlist.get()
    if len(h) == 0:
        usage('Error: missing hosts in -h and/or -f arguments')
    
    if len(h) == 1:
        print '[Warning] single host only - abandon netperf test'
        return (None, None, None)

    # start netserver
    netserver_port = startNetServer()
    if netserver_port == 0:
        print '[Error] unable to start netserver ... abort netperf test'
        return (None, None, None)
    
    netperf = copyExecOver(GV.opt['--netclient'])
    return (netperf, h, netserver_port)
    

#############
def runNetPerfTest():
    (netperf, hostlist, netserver_port) = setupNetPerfTest()
    if not netperf:
        return None

    # make sure we have even number of hosts
    odd = None
    h = hostlist[:]
    if len(h) & 1 == 1:
        odd = h.pop()           #  take one out

    # run netperf test between host[0] & host[1], host[2] & host[3], ...
    result = []
    for i in xrange(len(h)):
        if i & 1 == 0: continue
        x = h[i-1]
        y = h[i]
        res = runNetperfTestBetween(x, y, netperf, netserver_port)
	if res and len(res) >= 6: result.append(res)
        res = runNetperfTestBetween(y, x, netperf, netserver_port)
	if res and len(res) >= 6: result.append(res)

    # run netperf test between the odd host and host 0
    if odd:
        x = odd 
        y = h[0]
        res = runNetperfTestBetween(x, y, netperf, netserver_port)
        if res and len(res) >= 6: result.append(res)
        res = runNetperfTestBetween(y, x, netperf, netserver_port)
        if res and len(res) >= 6: result.append(res)

    return result


#############
def runNetPerfTestParallel():
    (netperf, hostlist, netserver_port) = setupNetPerfTest()
    if not netperf:
        return None

    # make sure we have even number of hosts
    h = hostlist[:]
    if len(h) & 1 == 1:
        h.append(h[0])

    # run netperf test between host[0] & host[1], host[2] & host[3], ...
    result = []
    procList = []
    for i in xrange(len(h)):
        if i & 1 == 0: continue
        x = h[i-1]
        y = h[i]
        p = spawnNetperfTestBetween(x, y, netperf, netserver_port, GV.opt['--duration'])
        if p[0]: 
            procList.append(p)
    
    for (proc, x, y) in procList:
        res = reapNetperfTest(proc, x, y)
	if res and len(res) >= 6: result.append(res)

    # second round: test between host[1] & host[0], host[3] & host[2], ...
    procList = []
    for i in xrange(len(h)):
        if i & 1 == 0: continue
        x = h[i-1]
        y = h[i]
        p = spawnNetperfTestBetween(y, x, netperf, netserver_port, GV.opt['--duration'])
        if p[0]: 
            procList.append(p)
    
    for (proc, x, y) in procList:
        res = reapNetperfTest(proc, x, y)
	if res and len(res) >= 6: result.append(res)


    return result

#############
def get_host_map(hostlist):
    '''
        Returns a seglist dictionary that maps segment name to hostname and
        a uniqhosts dictionary that maps each hostname to one segment name.

        For example:
        seglist = 
            seglist['sdw1-1'] = apollo1
            seglist['sdw1-2'] = apollo1
            seglist['sdw2-1'] = apollo2
            seglist['sdw2-2'] = apollo2
        uniqhosts =
            uniqhosts['apollo1'] = 'sdw1-1'
            uniqhosts['apollo2'] = 'sdw2-1'
            
    '''
    seglist   = dict()   # segment list
    uniqhosts = dict()   # unique host list

    # get list of hostnames
    rc, out = gpssh('hostname')
    if not rc:
        raise Exception('Encountered error running hostname')
        sys.exit(-1)

    # get unique hostname list
    for line in out.splitlines():
        seg, host = line.translate(None,'[]').split()
        uniqhosts[host] = seg

    # get list of segments associated with each host (can't use gpssh since it de-dupes hosts)
    for host in hostlist:
        cmd = ['ssh', '-o', 'BatchMode yes', '-o', 'StrictHostKeyChecking no', host, 'hostname' ]

        try:
            if GV.opt['-v']: print '[Info]', strcmd(cmd)
            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
            rc = proc.wait()
            if rc:
                raise Exception('ssh error with the following command:\n%s' % cmd)
            out = proc.stdout.read(-1)
            seglist[host] = out.strip()
        except KeyboardInterrupt:
            killProc(proc)
            raise
        except Exception, err:
            print err
            sys.exit(-1)

    return seglist, uniqhosts

#############
def runNetPerfTestMatrix():
    '''
        Runs full matrix network test where each host connnects with
            every link on every other host.  
        Returns performance results and a dictionary mapping segment_name
            to hostname
    '''
    (netperf, hostlist, netserver_port) = setupNetPerfTest()
    if not netperf:
        return None

    # dict() of seglist[segname] = hostname, uniqhosts[hostname] = 1 segment name
    seglist, uniqhosts = get_host_map(hostlist)

    # spawn netperf between all hosts 
    result = []
    procList = []
    for host in uniqhosts:
        for segment in seglist:
            if seglist[segment] == host:
                pass
            else:
                p = spawnNetperfTestBetween( host, segment, netperf, netserver_port, GV.opt['--duration'])
                if p[0]:
                    procList.append(p)

    for (proc, srcHost, dstHost) in procList:
        res = reapNetperfTest(proc, srcHost, dstHost)
	if res and len(res) >= 6: result.append(res)

    return result, seglist


#############
def printMatrixResult(result, seglist):

    print 'Full matrix netperf bandwidth test'

    # sum up Rx/Tx rate for each host
    netTx = dict()
    netRx = dict()
    for h in result:
        if netTx.has_key( h[0] ):
            netTx[ h[0] ] += float( h[6] )
        else:
            netTx[ h[0] ] = float( h[6] )

        # netRx requires that we lookup the hostname for a given segment name
        if netRx.has_key( seglist[ h[1] ] ):
            netRx[ seglist[ h[1] ] ] += float( h[6] )
        else:
            netRx[ seglist[ h[1] ] ] = float( h[6] )

    # print Tx rates 
    print "\nPer host transfer rates"
    for host in netTx:
        print '%s Tx rate: %.2f' % (host, netTx[host])
    print

    # print Rx rates
    print "Per host receive rates"
    for host in netRx:
        print '%s Rx rate: %.2f' % (host, netRx[host])
    print

    # print per link results in verbose mode
    if GV.opt['-v']: 
        for h in result:
            print '%s -> %s = %f' % (h[0], h[1], h[6])

    sum = min = max = avg = 0
    n = map(lambda x: float(x[6]), result)
    sum = reduce(lambda x, y: x + y, n)
    min = reduce(lambda x, y: x > y and y or x, n, 9999999999)
    max = reduce(lambda x, y: x > y and x or y, n)
    avg = float(sum) / len(result)
    
    copy = n[:]
    copy.sort()
    median = copy[len(copy) / 2]

    print ''
    print 'Summary:'
    print 'sum = %.2f MB/sec' % sum
    print 'min = %.2f MB/sec' % min
    print 'max = %.2f MB/sec' % max
    print 'avg = %.2f MB/sec' % avg
    print 'median = %.2f MB/sec' % median
    print ''


#############
def printNetResult(result):

    print 'Netperf bisection bandwidth test'
    for h in result:
        print '%s -> %s = %f' % (h[0], h[1], h[6])

    sum = min = max = avg = 0
    n = map(lambda x: float(x[6]), result)
    sum = reduce(lambda x, y: x + y, n)
    min = reduce(lambda x, y: x > y and y or x, n, 9999999999)
    max = reduce(lambda x, y: x > y and x or y, n)
    avg = float(sum) / len(result)
    
    copy = n[:]
    copy.sort()
    median = copy[len(copy) / 2]

    print ''
    print 'Summary:'
    print 'sum = %.2f MB/sec' % sum
    print 'min = %.2f MB/sec' % min
    print 'max = %.2f MB/sec' % max
    print 'avg = %.2f MB/sec' % avg
    print 'median = %.2f MB/sec' % median
    print ''

    limit = max * 0.9
    for r in result:
        if r[6] < limit:
            print '[Warning] connection between %s and %s is no good' % (r[0], r[1])



################
def printResult(title, result):
    totTime = 0
    totBytes = 0
    totMBPS = 0
    min = 99999999
    max = 0
    minHost = maxHost = None
    for h in result:
	(mbps, time, bytes) = result[h]
	totTime += time
	totBytes += bytes
	totMBPS += mbps
	if min > mbps: 
	    min = mbps
	    minHost = h
	if max < mbps: 
	    max = mbps
	    maxHost = h

    avgTime = totTime / len(result)

    print
    if (totTime > 0): print ' %s avg time (sec): %3.2f' % (title, avgTime)
    if (totBytes > 0): print ' %s tot bytes: %d' % (title, totBytes)
    print ' %s tot bandwidth (MB/s): %3.2f' % (title, totMBPS)
    print ' %s min bandwidth (MB/s): %3.2f [%s]' % (title, min, minHost)
    print ' %s max bandwidth (MB/s): %3.2f [%s]' % (title, max, maxHost)
    if GV.opt['-D']:
	print ' -- per host bandwidth --'
	for h in result:
	    (mbps, time, bytes) = result[h]
	    print '    %s bandwidth (MB/s): %3.2f [%s]' % (title, mbps, h)
    print


################

coverage = GpCoverage()
coverage.start()

try:
    parseCommandLine()
    runSetup()
    diskWriteResult = diskReadResult = streamResult = netResult = None
    tornDown = False
    try:
        if GV.opt['-r'].find('d') >= 0:
            multidd = copyExecOver('multidd')
            diskWriteResult = runDiskWriteTest(multidd)
            diskReadResult = runDiskReadTest(multidd)

        if GV.opt['-r'].find('s') >= 0:
            streamResult = runStreamTest()

        if GV.opt['--net'] == 'netperf':
            netResult = runNetPerfTest()
        elif GV.opt['--net'] == 'parallel':
            netResult = runNetPerfTestParallel()
        elif GV.opt['--net'] == 'matrix':
            netResult, seglist = runNetPerfTestMatrix()

        runTeardown()

    finally:
        print
        print '===================='
        print '==  RESULT'
        print '===================='

        if diskWriteResult:
            printResult('disk write', diskWriteResult)

        if diskReadResult:
            printResult('disk read', diskReadResult)

        if streamResult:
            printResult('stream', streamResult)

        if netResult and GV.opt['--net'] == 'matrix':
            printMatrixResult(netResult, seglist)
        elif netResult and GV.opt['--net']:
            printNetResult(netResult)

        runTeardown()

except KeyboardInterrupt:
    print '[Abort] Keyboard Interrupt ...'
finally:
    coverage.stop()
    coverage.generate_report()

