# run.py — CLI запуска нагрузочных сценариев. # # Примеры: # python3 -m loadtests.run baseline --devices 50 --rate 2 --duration 120 # python3 -m loadtests.run burst --devices 100 --rate 1 --burst-mult 10 --duration 30 # python3 -m loadtests.run large-payload --payload-size 900000 # python3 -m loadtests.run reconnect-storm --devices 100 --reconnect-every 30 # python3 -m loadtests.run multitenant --tenants 20 --devices-per-tenant 3 # python3 -m loadtests.run acl-violation # python3 -m loadtests.run auth-neg # python3 -m loadtests.run api-crud --threads 10 --iterations 20 # python3 -m loadtests.run telemetry-query --threads 10 --iterations 50 # python3 -m loadtests.run soak --devices 50 --rate 1 --duration 3600 --slice-sec 300 # # Общие ограничения: # - на устройство максимум ~900 сообщений за сценарий (API отдаёт ≤1000 строк); # - тест создаёт СВОИ устройства (namespace lt_*) и удаляет их при IOT_CLEANUP=1. import argparse import json from . import scenarios def main(): p = argparse.ArgumentParser(prog="loadtests") sub = p.add_subparsers(dest="scenario", required=True) def common(sp): sp.add_argument("--devices", type=int, default=50) sp.add_argument("--rate", type=float, default=1.0) sp.add_argument("--duration", type=float, default=60.0) sp.add_argument("--qos", type=int, choices=[0, 1], default=0) sp.add_argument("--payload-size", type=int, default=128) sp.add_argument("--reconnect-every", type=float, default=0.0) s = sub.add_parser("baseline", help="равномерная нагрузка N устройств") common(s) s = sub.add_parser("burst", help="всплеск: тихо → пик → тихо, замер recovery") common(s) s.add_argument("--burst-mult", type=float, default=10.0) s = sub.add_parser("large-payload", help="крупные payload (до ~900KB)") common(s) s = sub.add_parser("reconnect-storm", help="периодические разрывы соединений") common(s) s.set_defaults(reconnect_every=30, qos=1) s = sub.add_parser("multitenant", help="много namespace, задержка первого сообщения") s.add_argument("--tenants", type=int, default=20) s.add_argument("--devices-per-tenant", type=int, default=3) s.add_argument("--rate", type=float, default=0.2) s.add_argument("--duration", type=float, default=60.0) s.add_argument("--qos", type=int, choices=[0, 1], default=0) sub.add_parser("acl-violation", help="публикация в чужой топик → отказ") sub.add_parser("auth-neg", help="неверные креды → CONNACK 4/5") s = sub.add_parser("api-crud", help="нагрузка на CRUD устройств") s.add_argument("--threads", type=int, default=10) s.add_argument("--iterations", type=int, default=20) s = sub.add_parser("telemetry-query", help="нагрузка на чтение телеметрии") s.add_argument("--threads", type=int, default=10) s.add_argument("--iterations", type=int, default=50) s.add_argument("--query-limit", type=int, default=100) s.add_argument("--devices", type=int, default=10) s = sub.add_parser("soak", help="длительный прогон со срезами") common(s) s.add_argument("--slice-sec", type=int, default=300) args = p.parse_args() fn = getattr(scenarios, args.scenario.replace("-", "_")) report = fn(args) print(json.dumps(report, ensure_ascii=False, indent=2, default=str)) if __name__ == "__main__": main()