#!/usr/bin/python

import argparse
import collections
import json
import numpy
import sys
import xmlrpclib

class Measurement:
    def __init__(self, name):
        self.name = name
        self.measurements = []

    def add_measurement(self, m):
        self.measurements.append(m)

    def get_avg(self):
        return numpy.average(self.measurements)

    def get_std(self):
        return numpy.std(self.measurements)

class Formatter:
    def __init__(self, fname):
        self.fd = open(fname, 'w')

    def __del__(self):
        self.fd.close()

    def header(self, test_id, bigger_is_better):
        pass

    def measurement(self, name, avg, std):
        pass

    def write(self, str):
        self.fd.write(str)

class PlainFormatter(Formatter):
    def header(self, test_id, bigger_is_better):
        self.write("# test_id: %s\n" % test_id)
        self.write("# bigger_is_better: %s\n" % bigger_is_better)
        self.write("# test name          average        stdev        %stdev\n")

    def measurement(self, name, avg, std):
        cofv = std/avg #coefficient of variance
        self.write('{:>16}{:>12.3f}{:>13.2f}{:>13.2f}\n'.format(name, avg, std, cofv))

class WikiFormatter(Formatter):
    def header(self, test_id):
        self.write("== test_id: %s==\n" % test_id)
        self.write("'''Bigger is better''': %s\n" % bigger_is_better)
        self.write("| test name | average | stdev | %stdev|\n")

    def measurement(self, name, avg, std):
        cofv = std/avg #coefficient of variance
        self.write("| %s | %s | %s | %s |\n" % (name, avg, std, cofv))

class JSONFormatter(Formatter):

    def __init__(self, fname):
        Formatter.__init__(self, fname)
        self.write('{\n "benchmarks": [\n')
        self.in_header = False
        self.first_measurement = True

    def __del__(self):
        if self.in_header:
            self.write('\n    ]\n')
            self.write('  }\n')
        self.write(' ]\n}\n')
        Formatter.__del__(self)

    def header(self, test_id, bigger_is_better):
        if self.in_header:
            self.write('\n    ]\n')
            self.write('\n  },\n')
        self.write('  {\n')
        self.write('    "test_id" : "%s",\n' % test_id)
        self.write('    "bigger_is_better": "%s",\n' % bigger_is_better)
        self.write('    "test_results": [\n')
        self.in_header = True
        self.first_measurement = True

    def measurement(self, name, avg, std):
        if not self.first_measurement:
            self.write(',\n')
        self.write('        {\n')
        self.write('                 "name": "%s",\n' % name)
        self.write('                 "average": "%f",\n' % avg)
        self.write('                 "stdev": "%f"\n' % std)
        self.write('        }')
        self.first_measurement = False

def should_analyze(args, test_id):
    if args.test_id is None or len(args.test_id) is 0:
        return True
    for id in args.test_id:
        if id == test_id:
            return True
    return False

def add_measurement(results, test_id, test_result):
    name = test_result['test_case_id']
    if not results.has_key(test_id):
        results[test_id] = collections.OrderedDict()

    if not results[test_id].has_key(name):
        results[test_id][name] = Measurement(name)

    results[test_id][name].add_measurement(test_result['measurement'])

def get_totals_default(measurements):
    total = 0.0
    for m in measurements:
        total += m.get_avg()
    return total

def get_v8_totals(measurements):
    return measurements[-1].get_avg()

def add_totals(results):
    thismodule = sys.modules[__name__]

    for tr in results.keys():
        fname = "get_%s_totals" % tr
        func = getattr(thismodule, fname, get_totals_default)
        total = func(results[tr].values())
        test_result = {'test_case_id': tr, 'measurement': total}
        add_measurement(results, 'Totals', test_result)

# this allows us to flag test to know whether bigger measurements are good/bad
def bigger_is_better(test_id):
    if test_id == 'skia':
        return False
    return True

def main():
    p = argparse.ArgumentParser(description='parses lava results to get \
averages and standard deviations of data')
    p.add_argument('-j', dest='jsonfile',
                   help='Name of file to put the JSON formatted results in.')
    p.add_argument('-p', dest='plainfile',
                   help='Name of file to put the plain formatted results in.')
    p.add_argument('-w', dest='wikifile',
                   help='Name of file to put the wiki formatted results in.')
    p.add_argument('-t', action='append', dest='test_id',
                   help='The test_id to look for in the test_runs. ie "v8" \
This can be specified multiple times like "-t v8 -t skia". If none is \
specified it will parse all test results.')
    p.add_argument('bundles', metavar='N', nargs='+',
                   help='One or more bundles to pull test runs from.')
    args = p.parse_args()

    formatters = []
    if args.plainfile:
        formatters.append(PlainFormatter(args.plainfile))
    if args.wikifile:
        formatters.append(WikiFormatter(args.wikifile))
    if args.jsonfile:
        formatters.append(JSONFormatter(args.jsonfile))
    if len(formatters) is 0:
        print "ERROR: no file output specified"
        return

    server = xmlrpclib.ServerProxy("http://validation.linaro.org/lava-server/RPC2/")

    # a dict like: {
    #                'v8' : {
    #                    'testcase1': measurement,
    #                    'testcase2': measurement,
    #                }
    #              }
    results = {}

    # used for a sanity check to ensure we combine like benchmarks
    attributes = None

    for bundle in args.bundles:
        print "downloading bundle: ", bundle
        content = server.dashboard.get(bundle)['content']
        jso = json.loads(content)

        for tr in jso['test_runs']:
            if should_analyze(args, tr['test_id']):
                print " analyzing %s ..." % tr['test_id']
                if attributes is not None and attributes != tr['attributes']:
                    raise Exception("attribute mismatch!", attributes, tr['attributes'])
                attributes = tr['attributes']
                for result in tr['test_results']:
                    add_measurement(results, tr['test_id'], result)

    add_totals(results)

    for tr in results.keys():
        bisb = bigger_is_better(tr)
        for f in formatters:
            f.header(tr, bisb)
        for m in results[tr].keys():
            avg = results[tr][m].get_avg()
            std = results[tr][m].get_std()
            nam = results[tr][m].name
            for f in formatters:
                f.measurement(nam, avg, std)

if __name__ == "__main__":
    main()

