import itertools


def _analyze_spiky_throughput(
    throughputs, max_drop=0.5, min_duration=10, skip_initial_seconds=10
):  # pylint: disable=too-many-locals
    """
    Analyze throughput data for periods of reduced throughput. Any throughput value that is less
    than the average throughput of the entire run multiplied by `max_drop` is considered a "low"
    throughput; any period of successive low throughputs that's greather than or equal to
    `min_duration` (a number of seconds) is considered an error. Note that we may want to ignore the
    first few datapoints because the test is "warming up" and needs a little bit of time before
    hitting its initial maximum throughput; the amount of time to skip at the beginning of the test
    is specified in `skip_initial_seconds` in seconds (so if `skip_initial_seconds=20` the first 20
    seconds worth of datapoints will be skipped). The function returns a list of detailed error
    messages (`strings`), which is empty if no problems were detected in the throughput data.
    """

    err_messages = []

    # Skip datapoints based on `skip_initial_seconds`. `throughputs` is a list of `(time,
    # throughput)` tuples.
    while throughputs and throughputs[0].time <= skip_initial_seconds:
        throughputs = throughputs[1:]

    if not throughputs:
        return (
            True,
            (
                "Insufficient data to perform throughput analysis (less than {0} seconds of data "
                "was present)."
            ),
        )

    avg_throughput = float(sum(pair.ops for pair in throughputs)) / len(throughputs)
    min_acceptable_throughput = avg_throughput * max_drop
    throughputs_iter = iter(throughputs)

    for throughput in throughputs_iter:
        if throughput.ops < min_acceptable_throughput:
            first_low_throughput_time = throughput.time

            # Search until the point where performance numbers return to normal.
            low_throughputs = list(
                itertools.takewhile(
                    lambda throughput: throughput.ops < min_acceptable_throughput, throughputs_iter
                )
            )

            # If there aren't at least two consecutive low throughputs there aren't enough
            # datapoints to confidently flag a regression, no matter what the reporting interval is.
            if not low_throughputs:
                continue

            last_low_throughput_time = low_throughputs[-1].time
            duration = last_low_throughput_time - first_low_throughput_time
            if duration >= min_duration:
                # We've detected a long-enough period of reduced throughput.

                low_throughputs_str = "\n".join(
                    "    {0} sec: {1} ops/sec".format(time, throughput)
                    for time, throughput in low_throughputs
                )
                err_msg = (
                    "spiky throughput: Detected low throughput for {0} seconds, starting at {1} "
                    "seconds and ending at {2} seconds. The minimum acceptable throughput is {3} "
                    "ops/sec (the average throughput for the test was {4}ops/sec ), and the low "
                    "throughputs were: \n{5}\n"
                ).format(
                    duration,
                    first_low_throughput_time,
                    last_low_throughput_time,
                    min_acceptable_throughput,
                    avg_throughput,
                    low_throughputs_str,
                )
                err_messages.append(err_msg)

    return err_messages