#!/usr/bin/env python
"""
Find bottleneck from NetLogger transfer summary logs
"""
import logging
import operator
import sys
#
from netlogger.analysis import bottleneck
from netlogger.analysis.bottleneck import Event, Value
from netlogger.nllog import get_logger, OptionParser
from netlogger.parsers.base import NLFastParser

## Exceptions

class GIGO(Exception):
    EVENTS = (Event.DISK_READ, Event.DISK_WRITE,
              Event.NET_READ, Event.NET_WRITE)
    def _joinValues(self, v):
        return ', '.join([str(x) for x in v])

class WrongNumberOfValues(GIGO):
    def __init__(self, values):
        all = ', '.join(GIGO.EVENTS)
        value_str = self._joinValues(values)
        GIGO.__init__(self, "Wrong number of events. Got %d (%s), "
                      "expected 4 (%s)" % (len(values), value_str, all))

class DuplicateValues(GIGO):
    def __init__(self, value_set):
        GIGO.__init__(self, "%d duplicate events found in %s" % (
                    len(value_set), value_set))

## Functions

def parseError(line, linenum=0, error=""):
    log = get_logger(__file__)
    if log.isEnabledFor(logging.DEBUG):
        log.debug("parse.error", msg=str(error).replace('\n', ';;'))

def btlvalstr(v):
    return  "%lf MB/s (%lf Mbits/s)" % (v/1000000., v/1000000.*8)

def run(infile, outfile, algorithm=None, verbose=False):
    parser = NLFastParser(infile, err_cb=parseError)
    parsed = parser.parseStream()
    values = [ ]
    for n, event in enumerate(parsed):
        try:
            ecode = Event.get(event['event'])
        except KeyError:
            raise GIGO("Missing 'event' keyword in parsed event %d: %s" % (
                    n+1, event))
        if ecode:
            try:
                bytes = float(event['r.s'])
                sec = float(event['nv'])
            except KeyError:
                raise GIGO("event '%s' missing fields r.s and/or nv" % ecode)
            value = bytes / sec
            values.append(Value(ecode, value))
    if verbose:
        sorted_values = algorithm.sortedValues(values)
        for v in sorted_values:
            outfile.write("Event %s = %s\n" % (v.event, btlvalstr(v.value)))
    if len(values) != 4:
        raise WrongNumberOfValues(values)
    values_set = dict.fromkeys(map(operator.attrgetter('event'), values))
    if len(values_set) != 4:
        raise DuplicateValues(values_set)
    btl, reason = algorithm.calculate(values)
    if btl is None:
        outfile.write("Unknown bottleneck")
        if reason:
            outfile.write(": %s" % reason)
        outfile.write("\n")
    else:
        outfile.write("Bottleneck is: %s at %s" %
                      (btl.event, btlvalstr(btl.value)))
        if reason:
            outfile.write(", %s" % reason)
        outfile.write("\n")

def main(cmdline):
    usage = "%prog [options] [log-file]"
    desc = ' '.join(__doc__.split())
    parser = OptionParser(usage=usage, description=desc)
    parser.add_option('-a', '--algorithm', action="store", dest="alg",
                      type="choice", choices=("simple",), default="simple",
                      help="choose bottleneck algorithm by name "
                      "(default=%default)")
    parser.add_option('-d', '--debug', action="store_true", dest="debug",
                      default=False, help="log debugging information, "
                      "including parsing errors")
    parser.add_option('-r', '--report', action='store_true', dest='vb_report',
                      default=False,
                      help="print a longer report to the console")
    options, args = parser.parse_args(cmdline[1:])
    log = get_logger(__file__)  # Should be first done, just after parsing args
    if len(args) == 0:
        infile = sys.stdin
    else:
        infile = file(args[0])
    outfile = sys.stdout
    if options.alg == "simple":
        alg = bottleneck.Method1()
    else:
        parser.error("unknown bottleneck algorithm %s" % options.alg)
    # Run
    try:
        log.info("run.start", infile=infile.name, outfile=outfile.name,
                 verbose=options.vb_report)
        run(infile, outfile, algorithm=alg, verbose=options.vb_report)
        log.info("run.end", status=0)
    except GIGO, E:
        log.error("run.end", status=-1, msg="bad input: %s" % E)
        sys.stderr.write("Error in input: %s\n" % E)
        return 1

if __name__ == '__main__':
    sys.exit(main(sys.argv))
