Files
leocrm/scripts/seed_perf_data.py
T
leocrm-bot 69e91fd5d0 T10: Monitoring, Performance, Documentation & Environment Config — 38 tests, ruff clean, docs OK
- Extended health endpoint: DB+Redis+Storage+Worker checks with degraded status
- Prometheus metrics endpoint: admin-only, text/plain format
- Metrics: http_requests_total, db_pool_connections, arq_jobs_total
- Structured JSON logging (structlog): timestamp, level, method, path, status, duration_ms, tenant_id
- Performance: page_size max 100 enforced (422), streaming CSV export (StreamingResponse)
- Scripts: seed_perf_data.py, check_indexes.py
- Docs: admin-guide.md (Deploy, Backup, Restore, Env-Vars, Troubleshooting), api-overview.md
- README updated: prod setup, API section, env profiles, admin-guide link
- .env.example: added SECRET_KEY, STORAGE_PATH, SMTP_* vars
- 38 new tests, full regression 564/564 pass (0 failures)
- Ruff: all checks passed
2026-07-01 23:15:35 +02:00

140 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""Seed performance test data — creates N contacts in the test database.
Usage:
python scripts/seed_perf_data.py --count 200000
python scripts/seed_perf_data.py --count 5000 --tenant-id <uuid>
Requires DATABASE_URL environment variable or .env file.
"""
from __future__ import annotations
import argparse
import asyncio
import os
import sys
import uuid
from datetime import UTC, datetime
# Add project root to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from app.config import get_settings
from app.models.contact import Contact
from app.models.tenant import Tenant
from app.models.user import User
async def seed_contacts(count: int, tenant_id_str: str | None = None) -> None:
"""Seed `count` contacts into the database for performance testing."""
settings = get_settings()
engine = create_async_engine(
settings.database_url,
pool_size=settings.db_pool_size,
max_overflow=settings.db_max_overflow,
)
session_factory = async_sessionmaker(bind=engine, expire_on_commit=False)
async with session_factory() as session:
# Get or create a tenant
if tenant_id_str:
tenant_id = uuid.UUID(tenant_id_str)
result = await session.execute(select(Tenant).where(Tenant.id == tenant_id))
tenant = result.scalar_one_or_none()
if not tenant:
print(f"ERROR: Tenant {tenant_id_str} not found")
return
else:
# Get first tenant or create one
result = await session.execute(select(Tenant).limit(1))
tenant = result.scalar_one_or_none()
if not tenant:
tenant = Tenant(name="Perf Test Tenant", slug="perf-test")
session.add(tenant)
await session.flush()
# Get or create a user for this tenant
result = await session.execute(
select(User).where(User.tenant_id == tenant.id).limit(1)
)
user = result.scalar_one_or_none()
if not user:
from app.core.auth import hash_password
user = User(
tenant_id=tenant.id,
email="perf@test.local",
name="Perf Test User",
password_hash=hash_password("TestPass123!"),
role="admin",
is_active=True,
preferences={},
)
session.add(user)
await session.flush()
tenant_id = tenant.id
user_id = user.id
# Seed contacts in batches
batch_size = 1000
total_seeded = 0
first_names = ["Hans", "Maria", "Peter", "Anna", "Klaus", "Ursula", "Werner", "Brigitte", "Jürgen", "Helga"]
last_names = ["Müller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Wagner", "Becker", "Hoffmann", "Schäfer"]
while total_seeded < count:
batch = []
batch_count = min(batch_size, count - total_seeded)
for i in range(batch_count):
idx = total_seeded + i
fn = first_names[idx % len(first_names)]
ln = last_names[idx % len(last_names)]
batch.append(Contact(
tenant_id=tenant_id,
first_name=f"{fn}-{idx}",
last_name=f"{ln}-{idx}",
email=f"{fn.lower()}.{ln.lower()}-{idx}@example.com",
phone=f"+49-555-{idx:06d}",
created_by=user_id,
updated_by=user_id,
))
session.add_all(batch)
await session.commit()
total_seeded += batch_count
if total_seeded % 10000 == 0 or total_seeded == count:
print(f"Seeded {total_seeded}/{count} contacts...")
await engine.dispose()
print(f"Done: {total_seeded} contacts seeded.")
def main():
"""Parse arguments and run the seeding script."""
parser = argparse.ArgumentParser(
description="Seed performance test data into the LeoCRM database."
)
parser.add_argument(
"--count",
type=int,
default=200000,
help="Number of contacts to create (default: 200000)",
)
parser.add_argument(
"--tenant-id",
type=str,
default=None,
help="Tenant UUID to seed data into (default: first available tenant)",
)
args = parser.parse_args()
print(f"Starting seed: {args.count} contacts")
asyncio.run(seed_contacts(args.count, args.tenant_id))
if __name__ == "__main__":
main()