#!/usr/bin/env python3

# ===--- Benchmark_DTrace.in ---------------------------------------------===//
#
#  This source file is part of the Swift.org open source project
#
#  Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
#  Licensed under Apache License v2.0 with Runtime Library Exception
#
#  See https://swift.org/LICENSE.txt for license information
#  See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
#
# ===---------------------------------------------------------------------===//

import argparse
import os
import subprocess
import sys

DRIVER_LIBRARY_PATH = "@PATH_TO_DRIVER_LIBRARY@"
sys.path.append(DRIVER_LIBRARY_PATH)
DTRACE_PATH = os.path.join(DRIVER_LIBRARY_PATH, "swift_stats.d")

import perf_test_driver  # noqa (E402 module level import not at top of file)

# Regexes for the XFAIL_LIST. Matches against '([Onone|O|Osize],TestName)'
XFAIL_LIST = []


class DTraceResult(perf_test_driver.Result):
    def __init__(self, name, status, output, csv_output):
        perf_test_driver.Result.__init__(self, name, status, output, XFAIL_LIST)
        self.csv_output = csv_output

    def is_failure(self):
        return not bool(self.status)

    @classmethod
    def data_headers(cls):
        return ["Name", "Result", "Total RR Opts", "Total RR Opts/Iter"]

    @classmethod
    def data_format(cls, max_test_len):
        non_name_headers = DTraceResult.data_headers()[1:]
        fmt = ("{:<%d}" % (max_test_len + 5)) + "".join(
            ["{:<%d}" % (len(h) + 2) for h in non_name_headers]
        )
        return fmt

    @classmethod
    def print_data_header(cls, max_test_len, csv_output):
        headers = cls.data_headers()
        if csv_output:
            print(",".join(headers))
            return
        print(cls.data_format(max_test_len).format(*headers))

    def print_data(self, max_test_len):
        result = [self.get_name(), self.get_result()] + map(str, self.output)
        if self.csv_output:
            print(",".join(result))
            return

        print(DTraceResult.data_format(max_test_len).format(*result))


class DTraceBenchmarkDriver(perf_test_driver.BenchmarkDriver):
    def __init__(self, binary, xfail_list, csv_output):
        perf_test_driver.BenchmarkDriver.__init__(
            self, binary, xfail_list, enable_parallel=True, opt_levels=["O"]
        )
        self.csv_output = csv_output

    def print_data_header(self, max_test_len):
        DTraceResult.print_data_header(max_test_len, self.csv_output)

    def prepare_input(self, name):
        return {}

    def process_input(self, data):
        test_name = "({}_{})".format(data["opt"], data["test_name"])
        print("Running {}...".format(test_name))
        sys.stdout.flush()

        def get_results_with_iters(iters):
            e = os.environ
            e["SWIFT_DETERMINISTIC_HASHING"] = "1"
            p = subprocess.Popen(
                [
                    "sudo",
                    "dtrace",
                    "-s",
                    DTRACE_PATH,
                    "-c",
                    "%s %s %s %s"
                    % (
                        data["path"],
                        data["test_name"],
                        "--num-iters=%d" % iters,
                        "--num-samples=2",
                    ),
                ],
                stdout=subprocess.PIPE,
                stderr=open("/dev/null", "w"),
                env=e,
                universal_newlines=True,
            )
            results = [x for x in p.communicate()[0].split("\n") if len(x) > 0]
            return [
                x.split(",")[1] for x in results[results.index("DTRACE RESULTS") + 1 :]
            ]

        iter_2_results = get_results_with_iters(2)
        iter_3_results = get_results_with_iters(3)
        iter_5_results = get_results_with_iters(5)

        results = []
        foundInstability = False
        for x in zip(iter_2_results, iter_3_results, iter_5_results):
            result_2 = int(x[0])
            result_3 = int(x[1])
            result_5 = int(x[2])

            single_iter = result_3 - result_2
            two_iter = result_5 - result_3

            # We are always doing more work, so these should be the same. Fail
            # if we have a negative number.
            if single_iter < 0 or two_iter < 0:
                foundInstability = True

            # Our retain traffic should always increase linearly with iteration
            # size.
            if (single_iter * 2) == two_iter:
                foundInstability = True

            results.append(result_3)
            results.append(single_iter)

        return DTraceResult(
            test_name, int(not foundInstability), results, self.csv_output
        )


SWIFT_BIN_DIR = os.path.dirname(os.path.abspath(__file__))


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-filter",
        type=str,
        default=None,
        help="Filter out any test that does not match the given regex",
    )
    parser.add_argument(
        "--emit-csv",
        default=False,
        action="store_true",
        help="Emit csv output",
        dest="csv_output",
    )
    return parser.parse_args()


if __name__ == "__main__":
    args = parse_args()
    g = DTraceBenchmarkDriver(SWIFT_BIN_DIR, XFAIL_LIST, args.csv_output)
    if g.run(args.filter):
        sys.exit(0)
    else:
        sys.exit(-1)
