agriflow-api / benchmarks /latency.py
masterAAA123's picture
Space deploy v5: orphan snapshot of main, zero binary files
b81a86b
Raw
History Blame Contribute Delete
6.01 kB
"""
AgriFlow Matching Engine — Latency Benchmark
=============================================
Measure end-to-end latency dengan multiple konfigurasi untuk memvalidasi
klaim "<500ms p99 untuk 38 kab × 19 komoditas" (Section 5.5.4).
Run:
python benchmarks/latency.py
Output:
Tabel p50/p95/p99/max latency untuk:
- Sample data current (40 supply × 33 deficit)
- Stress: synthetic 38 kab × 19 komoditas full
- Stress: 100 supply × 100 deficit (large)
"""
from __future__ import annotations
import os
import statistics
import sys
import time
from datetime import datetime
# Force UTF-8 stdio di Windows.
if sys.platform == "win32":
try:
sys.stdout.reconfigure(encoding="utf-8")
except (AttributeError, OSError):
pass
# Add project root ke path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from matching_engine import (
Commodity, DemandNode, Kabupaten, LogisticsContext, SupplyNode, Tier,
run_matching,
)
from sample_data import load_all_sample_data
from sample_data.generate_sample_data import KABUPATEN_DATA, KOMODITAS_DATA
def percentile(data, p):
s = sorted(data)
k = (len(s) - 1) * p
f = int(k)
c = min(f + 1, len(s) - 1)
return s[f] + (s[c] - s[f]) * (k - f)
def bench_one(label: str, supply, demand, weather=None, historical=None,
warmup: int = 5, iterations: int = 100) -> dict:
logistics = LogisticsContext()
# Warmup
for _ in range(warmup):
run_matching(supply, demand, logistics=logistics,
weather_forecasts=weather, historical_prices=historical)
# Measure
samples = []
for _ in range(iterations):
t = time.perf_counter()
run_matching(supply, demand, logistics=logistics,
weather_forecasts=weather, historical_prices=historical)
samples.append((time.perf_counter() - t) * 1000)
return {
"label": label,
"n_supply": len(supply),
"n_demand": len(demand),
"iterations": iterations,
"p50_ms": percentile(samples, 0.50),
"p95_ms": percentile(samples, 0.95),
"p99_ms": percentile(samples, 0.99),
"min_ms": min(samples),
"max_ms": max(samples),
"mean_ms": statistics.mean(samples),
}
def make_synthetic_full_jatim():
"""38 kab × 19 komoditas: setengah surplus, setengah deficit per komoditas."""
kabs = []
for kid, nama, lat, lon, ipm, pop, tier_s in KABUPATEN_DATA:
kabs.append(Kabupaten(
id=kid, nama=nama, latitude=lat, longitude=lon, ipm=ipm,
tier=Tier.HIGH if tier_s == "TIER_1_HIGH" else Tier.MEDIUM,
population=pop,
))
komos = [
Commodity(code=c, nama=n, max_distance_km=md, min_viable_tons=mv,
max_fresh_age_days=mf)
for c, n, md, mv, mf, _baseline in KOMODITAS_DATA
]
surplus, deficit = [], []
for komo in komos:
for i, kab in enumerate(kabs):
if i % 2 == 0:
surplus.append(SupplyNode(
kabupaten=kab, commodity=komo,
volume_tons=max(komo.min_viable_tons * 5, 20.0),
price_per_kg=30000, harvest_age_days=1,
))
else:
deficit.append(DemandNode(
kabupaten=kab, commodity=komo,
volume_tons=max(komo.min_viable_tons * 4, 15.0),
price_per_kg=50000,
))
return surplus, deficit
def make_synthetic_large(n_supply: int = 100, n_demand: int = 100):
"""Stress test: scale beyond Jatim (project to national scale)."""
s, d = make_synthetic_full_jatim()
# Pad dengan duplikat (anggap nasional projection)
while len(s) < n_supply:
s.extend(s[:n_supply - len(s)])
while len(d) < n_demand:
d.extend(d[:n_demand - len(d)])
return s[:n_supply], d[:n_demand]
def print_row(r):
print(f" {r['label']:<35s} "
f"{r['n_supply']:>4d}×{r['n_demand']:<4d} "
f"p50={r['p50_ms']:>6.2f} p95={r['p95_ms']:>6.2f} "
f"p99={r['p99_ms']:>7.2f} max={r['max_ms']:>7.2f} "
f"mean={r['mean_ms']:>6.2f} ms")
def main():
print("=" * 110)
print(f" AgriFlow Matching Engine — Latency Benchmark "
f"({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})")
print("=" * 110)
print(f" Target Section 5.5.4: <500ms p99 untuk 38 kab × 19 komoditas")
print()
results = []
# 1. Sample data current
data = load_all_sample_data()
results.append(bench_one(
"Sample data CSV (realistic)",
data["surplus"], data["deficit"],
weather=data["weather"], historical=data["historical_prices"],
))
# 2. Synthetic 38 kab × 19 komoditas full
s_full, d_full = make_synthetic_full_jatim()
results.append(bench_one(
"Synthetic full Jatim (38×19)",
s_full, d_full,
))
# 3. Stress 100x100
s_100, d_100 = make_synthetic_large(100, 100)
results.append(bench_one(
"Stress 100×100 (national scale)",
s_100, d_100,
iterations=50,
))
# 4. Stress 200x200
s_200, d_200 = make_synthetic_large(200, 200)
results.append(bench_one(
"Stress 200×200",
s_200, d_200,
iterations=30,
))
print(f" {'Configuration':<35s} {'N (s×d)':>9s} {'p50':>9s} "
f"{'p95':>9s} {'p99':>11s} {'max':>11s} {'mean':>11s}")
print(" " + "-" * 106)
for r in results:
print_row(r)
print()
target = 500.0
p99_max = max(r["p99_ms"] for r in results)
if p99_max < target:
print(f" PASS semua konfigurasi p99 < {target}ms target "
f"(highest p99 = {p99_max:.2f}ms, "
f"margin {(target - p99_max) / target * 100:.1f}%)")
else:
print(f" FAIL p99 {p99_max:.2f}ms melebihi target {target}ms")
print()
if __name__ == "__main__":
main()