"""The aha, in four acts. Run: python3 demo.py

One fleet shape, one arrival trace, and the policies from load_balancer.py.
Nothing here has a clock or an unseeded RNG, so the output is byte-identical
on every run.
"""

from load_balancer import (Backend, RoundRobin, LeastConn, PeakEwma,
                           PeakEwmaOverSuccess, simulate, pct, row)

TICKS, LAM, SEED = 20000, 0.32, 1

SLOW = 10       # ticks per call, backends #1-#3
FAST = 1        # ticks per call, backend #4
N = 4


def fleet(healthy4, slow=SLOW, fast=FAST, slots=1, cv=0.0):
    """Three slow backends and one fast one. `healthy4` is the only bit that
    ever changes between the two worlds in act 1."""
    f = [Backend("ok%d" % i, slow, True, slots, cv) for i in range(N - 1)]
    f.append(Backend("fast3", fast, healthy4, slots, cv))
    return f


def run(policy, healthy4=False, **kw):
    return simulate(policy, fleet(healthy4, **kw), ticks=TICKS, lam=LAM, seed=SEED)


def policies():
    return [RoundRobin(), LeastConn(), PeakEwma(d=2), PeakEwma()]


def row4(r):
    """Like load_balancer.row, but reports fast3's share whether or not it is
    the broken one -- acts 1 and 2 are about that share being the same."""
    return ("  %-16s fast3=%6.2f%%  goodput=%5d/%d  p99(all)=%3.0f  p99(ok)=%3.0f"
            % (r["policy"], 100 * r["sent"][-1] / r["total"], r["good"],
               r["total"], pct(r["all"], .99), pct(r["ok"], .99)))


bar = "=" * 72

# --------------------------------------------------------------- act 1
print(bar)
print("ACT 1 -- the twin worlds")
print(bar)
print("%d backends: ok0/ok1/ok2 answer in %d ticks, fast3 answers in %d tick."
      % (N, SLOW, FAST))
print("Poisson(lam=%.2f), seed=%d, %d ticks. One boolean differs between the"
      % (LAM, SEED, TICKS))
print("two blocks below: fast3's `healthy`. Nothing else.")
for healthy in (True, False):
    print()
    print("--- fast3 healthy=%-5s (%s) ---"
          % (healthy, "genuinely 10x faster" if healthy else "errors after 1 tick"))
    for p in policies():
        print(row4(run(p, healthy)))

a, b = fleet(True), fleet(False)
ra, rb = (simulate(PeakEwma(), a, TICKS, LAM, SEED),
          simulate(PeakEwma(), b, TICKS, LAM, SEED))
print()
print("peak-ewma, the two worlds side by side:")
print("  requests routed, healthy=True : %s" % ra["sent"])
print("  requests routed, healthy=False: %s" % rb["sent"])
print("  balancer's belief, healthy=True : %s" % [round(x.ewma, 9) for x in a])
print("  balancer's belief, healthy=False: %s" % [round(x.ewma, 9) for x in b])
print("  routing decisions identical : %s" % (ra["sent"] == rb["sent"]))
print("  latency samples identical   : %s" % (ra["all"] == rb["all"]))
print("  goodput                     : %d  vs  %d" % (ra["good"], rb["good"]))

rr = run(RoundRobin(), False)
print()
print("The arithmetic (fast3 broken), peak-ewma against round-robin:")
print("  goodput   %4d -> %-4d  = %6.1fx WORSE"
      % (rr["good"], rb["good"], rr["good"] / rb["good"]))
print("  p99(all)  %4.0f -> %-4.0f  = %6.2fx BETTER"
      % (pct(rr["all"], .99), pct(rb["all"], .99),
         pct(rr["all"], .99) / pct(rb["all"], .99)))
print("  peak-ewma's %d is the literal sum of the healthy backends: %d + %d + %d"
      % (rb["good"], rb["sent"][0], rb["sent"][1], rb["sent"][2]))
print("  round-robin's %d is the 3-in-4 that missed it: %d x 3/4 = %.2f"
      % (rr["good"], rr["total"], rr["total"] * 3 / 4))

# --------------------------------------------------------------- act 2
print()
print(bar)
print("ACT 2 -- every latency metric ranks the policies backwards")
print(bar)
res = [run(p, False) for p in policies()]
print("ranked by p99 of ALL responses (what a latency dashboard shows):")
for r in sorted(res, key=lambda r: pct(r["all"], .99)):
    print("  %-16s p99=%3.0f ticks   goodput=%5d" % (r["policy"], pct(r["all"], .99), r["good"]))
print("ranked by p99 of SUCCESSFUL responses only (errors thrown away):")
for r in sorted(res, key=lambda r: pct(r["ok"], .99)):
    print("  %-16s p99=%3.0f ticks   goodput=%5d" % (r["policy"], pct(r["ok"], .99), r["good"]))
print("ranked by goodput:")
for r in sorted(res, key=lambda r: -r["good"]):
    print("  %-16s goodput=%5d   p99(all)=%3.0f" % (r["policy"], r["good"], pct(r["all"], .99)))

# --------------------------------------------------------------- act 3
print()
print(bar)
print("ACT 3 -- power-of-d-choices is the blast radius, and it is exactly d/N")
print(bar)


class Counted(PeakEwma):
    """The shipped PeakEwma, plus two counters over its own candidate set."""

    def __init__(self, d):
        PeakEwma.__init__(self, d)
        self.sampled = self.won = 0

    def pick(self, fleet, now, rng):
        w = PeakEwma.pick(self, fleet, now, rng)
        if any(not x.healthy for x in self.last):
            self.sampled += 1
            self.won += not w.healthy
        return w


print("  %-4s %-4s %10s %10s %10s %14s" %
      ("N", "d", "pred d/N", "measured", "goodput", "won|sampled"))
for n in (4, 8, 16):
    for d in range(1, n + 1):
        if n > 4 and d not in (1, 2, 3, 4, 6, 8, 12, 16):
            continue
        f = [Backend("ok%d" % i, SLOW, True) for i in range(n - 1)]
        f.append(Backend("bad", FAST, False))
        p = Counted(d)
        r = simulate(p, f, TICKS, LAM, SEED)
        print("  %-4d %-4d %9.2f%% %9.2f%% %10d %14s"
              % (n, d, 100 * d / n, 100 * r["broken_share"], r["good"],
                 "%d/%d" % (p.won, p.sampled)))
    print()
print("  The broken backend wins every sample it appears in, so its share is")
print("  just the probability of being sampled: d/N. More choices is more")
print("  chances for the beacon to be visible.")

# --------------------------------------------------------------- act 4
print()
print(bar)
print("ACT 4 -- the boundary, and the one-term fix")
print(bar)
print("peak-ewma's share of the broken backend, by how long failing takes.")
print("1/N = %.2f%%: at that share the policy IS round-robin." % (100 / N))
print("  %-8s %s" % ("service", "error latency ->"))
for s in (5, 10, 20, 40):
    cells = []
    for e in (1, s // 2, s - 1, s, s + 1, 2 * s):
        r = run(PeakEwma(), False, slow=s, fast=e)
        cells.append("e=%-3d %5.1f%%" % (e, 100 * r["broken_share"]))
    print("  s=%-6d %s" % (s, "  ".join(cells)))
print()
print("Past the crossover the sign flips -- latency-aware balancing becomes the")
print("best failure detector in the fleet (service=%d):" % SLOW)
for e in (1, SLOW, 50):
    x, y = run(PeakEwma(), False, fast=e), run(RoundRobin(), False, fast=e)
    print("  error latency %-3d ticks: peak-ewma goodput %5d   round-robin %5d   (%+.1f%%)"
          % (e, x["good"], y["good"], 100 * (x["good"] / y["good"] - 1)))
print()
print("The fix is one term: divide the score by the observed success rate.")
for p in (RoundRobin(), PeakEwma(), PeakEwmaOverSuccess()):
    print(row(run(p, False)))
