Files
IoT/loadtests/run.py
T

83 lines
3.7 KiB
Python

# 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()