#!/usr/bin/env python3
"""Deterministic capacity models for the performance topic.

Each case recomputes the numbers derived in the topic articles from explicit
inputs. These are fluid/probability teaching models: no threads, real latency
distributions, load generators or production measurements are simulated.
"""
import argparse
import json


def workload(arrival_peak=600, capacity=150, arrival_normal=100, burst_seconds=60):
    """Fluid backlog during a burst, drain time and approximate tail wait."""
    backlog = (arrival_peak - capacity) * burst_seconds
    if arrival_normal >= capacity:
        drain = None
        tail_wait = None
    else:
        drain = backlog / (capacity - arrival_normal)
        tail_wait = backlog / capacity
    return {
        'model': 'fluid-backlog-not-scheduler',
        'inputs': {'arrival_peak_per_s': arrival_peak, 'capacity_per_s': capacity,
                   'arrival_normal_per_s': arrival_normal, 'burst_s': burst_seconds},
        'backlog_items': backlog,
        'drain_s': drain,
        'tail_wait_s': tail_wait,
        'note': 'drain uses net rate (capacity-arrival); tail wait uses service rate',
    }


def percentile(success=9800, success_ms=20, timeouts=200, timeout_ms=1000, layers=3, per_layer=3):
    """Right-censored P99 warning and worst-case retry amplification."""
    total = success + timeouts
    on_time_rate = success / total
    worst_attempts = per_layer ** layers
    return {
        'model': 'censored-quantile-not-latency-distribution',
        'inputs': {'success': success, 'success_ms': success_ms,
                   'timeouts': timeouts, 'timeout_ms': timeout_ms},
        'success_subset_p99_ms': success_ms,
        'on_time_success_rate': on_time_rate,
        'timeouts': timeouts,
        'timeout_completion_ms': None,
        'worst_case_attempts': worst_attempts,
        'note': 'timeout completion is unknown (censored); never fill it with 0 or timeout_ms',
    }


def failure(zones=3, per_zone=4, per_instance=500, peak=4000, retry=1.1,
            normal_hit=0.95, fail_hit=0.60, reads_per_miss=2, writes_per_write=3,
            write_share=0.10, reserve=0.80):
    """Zone failure: surviving capacity, DB read/write amplification, fleet size."""
    total_instances = zones * per_zone
    normal_capacity = total_instances * per_instance
    surviving = normal_capacity - zones * 0 * per_zone  # one zone lost
    surviving = (zones - 1) * per_zone * per_instance
    attempts = peak * retry
    db_reads_normal = peak * (1 - write_share) * (1 - normal_hit) * reads_per_miss
    db_writes_normal = peak * write_share * writes_per_write
    db_reads_fail = attempts * (1 - write_share) * (1 - fail_hit) * reads_per_miss
    db_writes_fail = attempts * write_share * writes_per_write
    target_capacity = attempts / reserve
    instances_needed = -(-target_capacity // per_instance)
    per_zone_needed = -(-instances_needed // (zones - 1))
    fleet = int(per_zone_needed) * (zones - 1) + 0  # keep N-1 zones serving
    fleet = int(per_zone_needed * zones)
    return {
        'model': 'zone-failure-fluid-not-drill',
        'inputs': {'zones': zones, 'per_zone': per_zone, 'per_instance': per_instance,
                   'peak': peak, 'retry': retry, 'normal_hit': normal_hit,
                   'fail_hit': fail_hit, 'reserve': reserve},
        'normal_capacity_per_s': normal_capacity,
        'surviving_capacity_per_s': surviving,
        'attempts_per_s': attempts,
        'db_reads_normal_per_s': db_reads_normal,
        'db_writes_normal_per_s': db_writes_normal,
        'db_reads_fail_per_s': db_reads_fail,
        'db_writes_fail_per_s': db_writes_fail,
        'target_capacity_per_s': target_capacity,
        'instances_needed_total': int(fleet),
        'note': '80% reserve is a case choice, not a utilization law; cold cache assumed at fail_hit',
    }


def resource(qps=600, cpu_ms=3, read_kib=200, conn_hold_ms=20, cores=8,
             compress_extra_cpu_ms=5, cache_hit=0.60,
             cost_app=12000, cost_storage=6000, cost_traffic=8000, cost_ops=4000,
             success_normal=90_000_000, success_bad=60_000_000, cost_after=27_000):
    """Per-request resource demand and unit cost under a fixed window."""
    cpu_cores = qps * cpu_ms / 1000
    net_mib_s = qps * read_kib / 1024
    conns = qps * conn_hold_ms / 1000
    cpu_compressed = qps * (cpu_ms + compress_extra_cpu_ms) / 1000
    net_cached = qps * read_kib / 1024 * (1 - cache_hit)
    cost_total = cost_app + cost_storage + cost_traffic + cost_ops
    unit_normal = cost_total / (success_normal / 1_000_000)
    unit_after = cost_after / (success_bad / 1_000_000)
    return {
        'model': 'mean-demand-not-tail-profile',
        'inputs': {'qps': qps, 'cpu_ms': cpu_ms, 'read_kib': read_kib,
                   'conn_hold_ms': conn_hold_ms},
        'cpu_cores': cpu_cores,
        'network_mib_s': net_mib_s,
        'mean_connections': conns,
        'cpu_cores_with_compression': cpu_compressed,
        'network_mib_s_with_cache': net_cached,
        'unit_cost_per_million_normal': unit_normal,
        'unit_cost_per_million_after': unit_after,
        'note': 'means only; tails, bursts and contention need separate measurement',
    }


def network(backlog=240_000, arrival=800, consumer=1000,
            db_limit=3600, accesses_per_event=3, reserve_db=600, lean_accesses=2):
    """Shared-downstream backlog drain with and without reservations."""
    def drain(rate):
        return None if rate <= arrival else backlog / (rate - arrival)
    db_cap_events = db_limit / accesses_per_event
    naive = backlog / consumer
    with_reserve = drain(min(consumer, (db_limit - reserve_db) / accesses_per_event))
    lean_cap = (db_limit - reserve_db) / lean_accesses
    # The lean variant assumes consumers can be scaled up to the DB cap.
    lean_drain = drain(lean_cap)
    required_consumer = arrival + backlog / 600  # 10-minute target from the article
    return {
        'model': 'shared-bottleneck-drain-not-load-test',
        'inputs': {'backlog': backlog, 'arrival_per_s': arrival,
                   'consumer_per_s': consumer, 'db_access_limit': db_limit,
                   'accesses_per_event': accesses_per_event, 'reserved_db': reserve_db},
        'db_event_cap_per_s': db_cap_events,
        'drain_s_base': drain(consumer),
        'naive_drain_s_ignoring_arrival': naive,
        'drain_s_db_capped_consumer2000': drain(db_cap_events),
        'drain_s_with_reservation': with_reserve,
        'drain_s_lean_two_accesses': lean_drain,
        'consumer_needed_for_600s': required_consumer,
        'db_needed_for_600s': required_consumer * accesses_per_event + reserve_db,
        'note': 'consumer scaled beyond DB cap does not add drain capacity',
    }


def shape(burst_seconds=60, arrival_peak=600, capacity=150, arrival_normal=100,
          retry_share=0.10):
    """U70：形状参数敏感性——突发时长、常规到达与重试份额。"""
    base_backlog = (arrival_peak - capacity) * burst_seconds
    retry_arrival = arrival_peak * (1 + retry_share)
    retry_backlog = (retry_arrival - capacity) * burst_seconds
    normal_retry = arrival_normal * (1 + retry_share)
    result = {
        'model': 'shape-sensitivity-fluid',
        'burst20': {'backlog': (arrival_peak - capacity) * 20,
                    'drain_s': (arrival_peak - capacity) * 20 / (capacity - arrival_normal)},
        'normal140': {'drain_s': base_backlog / (capacity - 140)},
        'retry10': {'backlog': retry_backlog,
                    'drain_s': retry_backlog / (capacity - normal_retry)},
    }
    assert result['burst20'] == {'backlog': 9000, 'drain_s': 180.0}
    assert result['normal140']['drain_s'] == 2700.0
    assert result['retry10']['backlog'] == 30600
    assert abs(result['retry10']['drain_s'] - 765.0) < 1e-9
    return result


def parallel(qps=1000, hold_ms=10, safe=24, fault_hold_ms=40):
    """U70：在途数、连接占用与下游安全并发决定的上限。"""
    result = {
        'model': 'downstream-safe-concurrency',
        'lambda_w': qps * 0.040,
        'connections_normal': qps * hold_ms / 1000,
        'connections_fault': qps * fault_hold_ms / 1000,
        'db_capacity_normal': safe / (hold_ms / 1000),
        'db_capacity_fault': safe / (fault_hold_ms / 1000),
        'overload_fault': qps - safe / (fault_hold_ms / 1000),
    }
    assert result['lambda_w'] == 40 and result['connections_normal'] == 10
    assert result['connections_fault'] == 40 and result['db_capacity_normal'] == 2400
    assert result['db_capacity_fault'] == 600 and result['overload_fault'] == 400
    return result


def quantile_samples(low_qps=30):
    """U70：P99/P99.9 最小样本量与低峰窗口。"""
    import math
    n99 = math.log(0.1) / math.log(0.99)
    n999 = math.log(0.1) / math.log(0.999)
    result = {'model': 'quantile-sample-size', 'n_p99': math.ceil(n99),
              'n_p999': math.ceil(n999),
              'seconds_p999': round(math.ceil(n999) / low_qps, 1)}
    assert result['n_p99'] == 230 and result['n_p999'] == 2302
    assert result['seconds_p999'] == 76.7
    return result


def reserve_reuse(peak=4000, retry=1.1, reserve=0.80, per_instance=500,
                  takeover=400, rebuild=500, catchup=400, db_limit=3500,
                  normal_hit=0.95, fail_hit=0.60):
    """U70：同一份 20% 余量同时承诺接管、重建与追赶。"""
    attempts = peak * retry
    target = attempts / reserve
    import math
    instances_needed = math.ceil(target / per_instance)   # 11
    per_zone = math.ceil(instances_needed / 2)            # 失一区后由两区承担 → 每区 6
    fleet = per_zone * 3
    survivor = (fleet - per_zone) * per_instance
    headroom = survivor - attempts
    db_normal = peak * 0.9 * (1 - normal_hit) * 2 + peak * 0.1 * 3
    db_fault = attempts * 0.9 * (1 - fail_hit) * 2 + attempts * 0.1 * 3
    result = {
        'model': 'reserve-not-reusable',
        'fleet_total': fleet,
        'fleet_survivor_capacity': survivor,
        'fleet_headroom': headroom,
        'workloads_sum': takeover + rebuild + catchup,
        'workloads_rebuild1200': takeover + 1200 + catchup,
        'db_total_normal': db_normal,
        'db_total_fault': db_fault,
        'db_deficit': db_fault - db_limit,
        'db_with_background': db_fault + rebuild + catchup,
    }
    assert fleet == 18 and survivor == 6000 and headroom == 1600
    assert result['workloads_sum'] == 1300 and result['workloads_rebuild1200'] == 2000
    assert round(db_normal, 6) == 1560 and round(db_fault, 6) == 4488 and result['db_deficit'] == 988
    assert result['db_with_background'] == 5388
    return result


def profile_inputs(qps=600, read_kib=200, cache_hit=0.60,
                   cost_total=30_000, success_normal=90_000_000,
                   cost_after=27_000, success_bad=60_000_000):
    """U70：画像输入替换——对象大小、命中率与成功定义。"""
    base_net = qps * read_kib / 1024
    result = {
        'model': 'profile-input-sensitivity',
        'network_200kib': base_net,
        'network_400kib': qps * read_kib * 2 / 1024,
        'network_cache60': base_net * (1 - cache_hit),
        'network_cache0': base_net,
        'unit_normal': round(cost_total / (success_normal / 1_000_000), 2),
        'unit_after': round(cost_after / (success_bad / 1_000_000), 2),
    }
    assert result['network_200kib'] == 117.1875 and result['network_400kib'] == 234.375
    assert result['network_cache60'] == 46.875 and result['unit_normal'] == 333.33
    assert result['unit_after'] == 450.0
    return result


def fanout_poison(backlog=240_000, arrival=800, db_limit=3600, base_accesses=3,
                  poison_share=0.02, poison_attempts=3, fan_share=0.05, fan_multiplier=10):
    """U70：毒消息与扇出按等价访问量改写净追赶。"""
    poison_accesses = (1 - poison_share) * base_accesses + poison_share * base_accesses * poison_attempts
    fan_accesses = (1 - fan_share) * base_accesses + fan_share * base_accesses * fan_multiplier
    def drain(accesses):
        cap = db_limit / accesses
        return {'accesses': round(accesses, 2), 'cap': round(cap, 2),
                'drain_s': round(backlog / (cap - arrival), 1) if cap > arrival else None}
    result = {'model': 'equivalent-access-drain', 'poison_2pct': drain(poison_accesses),
              'fanout_5pct': drain(fan_accesses)}
    assert result['poison_2pct']['accesses'] == 3.12
    assert result['poison_2pct']['cap'] == 1153.85 and result['poison_2pct']['drain_s'] == 678.3
    assert result['fanout_5pct']['accesses'] == 4.35
    assert result['fanout_5pct']['cap'] == 827.59 and result['fanout_5pct']['drain_s'] == 8700.0
    return result


def stack_amplification(qps=2000, fanout=21, miss=0.10, hold_ms=15,
                        fault_hold_ms=150, safe=60):
    """U70：N+1、冷缓存与故障持有共同击穿连接预算。"""
    result = {
        'model': 'stacked-amplification',
        'sql_unfixed': qps * fanout,
        'sql_cached': qps * miss,
        'sql_cold': qps,
        'connections_normal': qps * hold_ms / 1000,
        'connections_fault': qps * fault_hold_ms / 1000,
        'db_capacity_normal': safe / (hold_ms / 1000),
        'db_capacity_fault': safe / (fault_hold_ms / 1000),
        'unserved_fault': qps - safe / (fault_hold_ms / 1000),
    }
    assert result['sql_unfixed'] == 42000 and result['sql_cached'] == 200
    assert result['connections_fault'] == 300 and result['db_capacity_fault'] == 400
    assert result['unserved_fault'] == 1600
    return result


def main():
    checks = {'workload': workload, 'percentile': percentile,
              'failure': failure, 'resource': resource, 'network': network,
              'shape': shape, 'parallel': parallel, 'quantile_samples': quantile_samples,
              'reserve_reuse': reserve_reuse, 'profile_inputs': profile_inputs,
              'fanout_poison': fanout_poison, 'stack_amplification': stack_amplification}
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('case', choices=[*checks, 'all'])
    args = parser.parse_args()
    if args.case == 'all':
        print(json.dumps({name: fn() for name, fn in checks.items()},
                         ensure_ascii=False, indent=2))
    else:
        print(json.dumps(checks[args.case](), ensure_ascii=False, indent=2))


if __name__ == '__main__':
    main()
