Files
MathApp/tools/loadtest/README.md
Zareian a4e0c576bb Docs brought to the current state: CLAUDE.md rewritten, README formal, handbook and diagrams redrawn
- CLAUDE.md: 1,225 → ~430 lines, current state only (the dated status log stays in git history).
  Server rules moved to the top: what is ours on 249/247 and what is not, non-negotiable rules,
  announce-before-acting, password handling, and the full "deploy it" procedure with verification.
- README: formal entry page with current numbers (10 grades, 18,522 questions, 61 API operations,
  973 tests, PostgreSQL/Redis).
- Removed docs/frontend/ (Figma-era guide with guest login and its own api.ts; the frontend repo has
  the real client). Swagger's external-docs link now points at the frontend repo.
- architecture/operations/security/content-authoring/loadtest/textbooks: SQLite, guest login,
  demo accounts, question packs and the port-80 note replaced by what is true now.
- Technical handbook text updated throughout; all seven diagrams redrawn (PostgreSQL, phone-only
  login, subscriptions, current data model); PDF rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:45:51 +03:30

5.6 KiB
Raw Permalink Blame History

لودتستِ دستیار

سه اسکریپت، برای اینکه «چند دانش‌آموزِ هم‌زمان» هر بار با یک روش سنجیده شود. خروجیِ هر اجرا یک JSON است با زمانِ هر پرسش و هر درخواستِ صفحه‌ی عادی؛ جمع‌بندی در خودِ خروجیِ ترمینال چاپ می‌شود.

فایل کار
mkusers.py کاربرانِ آزمایشی (شماره‌ی جعلیِ 0900…، بی‌پیامک، بی‌سیگنال) با توکن و سه سوالِ متفاوت از بانکِ سوال برای هر کدام
loadtest.py N نفر هم‌زمان می‌پرسند (استریم، دقیقاً مثلِ فرانت) و B نفرِ دیگر در خانه/آزمون/خلاصه/سهمیه می‌چرخند
fake_upstream.py جایگزینِ GapGPT و Qdrant برای آزمایشگاه: embedding، جوابِ استریم با زمانِ لاگ‌نرمال (پیش‌فرض: میانه‌ی ۵٫۲ و صدکِ ۹۰ِ ۱۳ ثانیه، از جواب‌های واقعیِ سرور) و جست‌وجوی Qdrant

آزمایشگاه (بی‌هزینه، روی لپ‌تاپ)

روی یک کپی از پروژه اجرا کنید تا دیتابیسِ اصلیِ لوکال پر نشود. Qdrant هم شبیه‌سازی می‌شود، چون از لپ‌تاپ پهنای باندِ بالارفتن (هر بردار ~۳۰ کیلوبایت) جست‌وجوها را سریالی نشان می‌دهد؛ از خودِ سرور ۲۰ جست‌وجوی هم‌زمان ۰٫۲۶ ثانیه است.

python tools/loadtest/fake_upstream.py &                      # :8901 (FAKE_SIGMA=0 یعنی همیشه ۵٫۲ ثانیه)
export DJANGO_DEBUG=0 CACHE_URL= NUM_PROXIES=0 AI_DAILY_QUOTA_FREE=1000 HEARTS_ENFORCED=0 RAG_FORCE_MOCK=0 \
       DJANGO_ALLOWED_HOSTS=127.0.0.1 OPENAI_BASE_URL=http://127.0.0.1:8901/v1 OPENAI_API_KEY=x \
       QDRANT_URL=http://127.0.0.1:8901 QDRANT_API_KEY=x
LT_N=150 python manage.py shell -c "exec(open('tools/loadtest/mkusers.py').read())" | grep ^LTJSON | cut -c7- > users.json
gunicorn --bind 127.0.0.1:8800 config.wsgi:application &      # همان gunicorn.conf.py ِ سرور
python tools/loadtest/loadtest.py http://127.0.0.1:8800 users.json 50 10 0 out.json --writes

پیش از هر اجرا gunicorn را از نو بالا بیاورید (کشِ جوابِ هر worker خالی شود) و مطمئن شوید پروسه‌ی قبلی واقعاً رفته است؛ وگرنه سوال‌های تکراری از کش در ۰٫۱ ثانیه جواب می‌گیرند و عدد دروغ می‌گوید.

سرورِ اصلی (هزینه‌ی واقعی: ~۰٫۱۸ سنت برای هر جواب)

۱. بکاپ از دیتابیس (docs/operations.md). ۲. کاربرانِ آزمایشی را روی سرور با mkusers.py بسازید (LT_PREFIX یک پیشوندِ جعلی که روی سرور نیست) و خروجی را فقط در یک فایلِ 600 نگه دارید. ۳. loadtest.py را با https://mathapi.tadrisino2.ir و بدونِ --writes اجرا کنید. ۴. زمانِ داخلِ GapGPT برای هر پیام در ai_tutor.Message.latency_ms است؛ اختلافِ آن با زمانِ کلاینت یعنی کارِ سرورِ ما و صفِ انتظار برای نخِ آزاد. ۵. manage.py delete_users --ids … --yes و پاک کردنِ فایلِ توکن‌ها.

روی سرورِ مشترک، throttleهای پروداکشن دست نمی‌خورند. اگر سقفِ نرخ مانع است، یک gunicornِ موقت روی سوکتِ جدا در ~/mathapp-v2/.lt/ با READ_THROTTLE_RATE بالا و DB_POOL_MAX_SIZE=4 --workers 2 بالا بیاورید (تا با ۳۲ اتصالِ پروداکشن از ۵۷ نگذرد)، کوتاه اجرا کنید و آخرش .lt را پاک کنید. در اسکریپت روی آن gunicornِ پس‌زمینه wait نزنید: تا ابد منتظر می‌ماند.

بازیکن‌ها (نه دستیار): mkplayers.py، play.py، agg.py

هر کاربرِ مجازی خانه ← آزمونِ یک جزیره ← ۴ جواب ← درس ← خلاصه را می‌چرخد، با مکثِ تصادفیِ ۱ تا THINK_MAX ثانیه. کاربرها اشتراکِ آزمایشیِ یک‌روزه می‌گیرند (بی دیوارِ پرداخت و بی کمبودِ قلب). روی سرور، مستقیم روی سوکتِ gunicorn:

cd ~/mathapp-v2 && mkdir -m 700 .lt     # اول بکاپِ دیتابیس
LT_N=1000 LT_PREFIX=0900770 venv/bin/python manage.py shell -c "exec(open('tools/loadtest/mkplayers.py').read())" \
  | grep ^LTJSON | cut -c7- > .lt/users.json
for p in $(seq 0 9); do venv/bin/python tools/loadtest/play.py /run/gunicorn/mathapp.sock .lt/users.json .lt/out-$p.json \
  "300:40,600:40,1000:60" 5 40 $p 10 & done; wait
venv/bin/python tools/loadtest/agg.py ".lt/out-*.json" "300:40,600:40,1000:60"

مولد را چندپروسه‌ای اجرا کنید (آخرین دو آرگومان). یک پروسه‌ی httpx با ۶۰۰ اتصال خودش گلوگاه شد: کلاینت ۲۱ ثانیه دید درحالی‌که لاگِ اپ برای همان درخواست‌ها ۰٫۲ تا ۰٫۵ ثانیه می‌گفت (۲۰۲۶-۱۰-۱۳). همیشه duration_msِ logs/app.log (به وقتِ UTC) را با عددِ کلاینت مقایسه کنید. پاک کردن: delete_users از راهِ پایتون روی phone__startswith=LT_PREFIX (فهرستِ id از manage.py shell با خطِ هشدار قاطی می‌شود)، کشِ رتبه‌بندی، و rm -rf .lt.