154 lines
5.8 KiB
Python
154 lines
5.8 KiB
Python
import asyncio
|
||
import os
|
||
import time
|
||
import json
|
||
import random
|
||
from datetime import datetime, timezone
|
||
from sqlalchemy import insert, select, text
|
||
from sqlalchemy.ext.asyncio import create_async_engine
|
||
from sqlalchemy.engine.url import make_url
|
||
from backend.database.schema import vehicle_signals, Simulation, init_schema, init_timescaledb
|
||
from backend.config.settings import load_settings
|
||
|
||
# 模拟数据生成
|
||
def generate_payload():
|
||
return {
|
||
"steering_wheel_angle_deg": round(random.uniform(-450, 450), 1),
|
||
"brake_pedal_travel_mm": round(random.uniform(0, 100), 1),
|
||
"throttle_pedal_travel_mm": round(random.uniform(0, 100), 1),
|
||
"gear": random.choice(["P", "N", "D", "R"]),
|
||
"handbrake": random.choice([0, 1]),
|
||
"vehicle_speed_kmh": round(random.uniform(0, 180), 1),
|
||
"wheel_speed_rpm": {
|
||
"FL": random.randint(0, 2000),
|
||
"FR": random.randint(0, 2000),
|
||
"RL": random.randint(0, 2000),
|
||
"RR": random.randint(0, 2000)
|
||
},
|
||
"lights": {
|
||
"left_turn": random.choice([0, 1]),
|
||
"right_turn": random.choice([0, 1]),
|
||
"hazard": random.choice([0, 1]),
|
||
"brake": random.choice([0, 1])
|
||
},
|
||
"soc_percent": round(random.uniform(0, 100), 1),
|
||
"voltage_v": round(random.uniform(300, 400), 1),
|
||
"current_a": round(random.uniform(-50, 200), 1),
|
||
"temperature_c": round(random.uniform(20, 80), 1)
|
||
}
|
||
|
||
def _redact_url(url: str) -> str:
|
||
try:
|
||
parsed = make_url(url)
|
||
if parsed.password:
|
||
parsed = parsed.set(password="***")
|
||
return str(parsed)
|
||
except Exception:
|
||
return url
|
||
|
||
async def run_test():
|
||
settings = load_settings()
|
||
|
||
db_url = os.getenv("SMARTEDT_TEST_DATABASE_URL", settings.database.url).strip()
|
||
print(f"Connecting to DB: {_redact_url(db_url)}")
|
||
engine = create_async_engine(db_url, echo=False)
|
||
|
||
# 0. 初始化表结构 (如果不存在)
|
||
print("Initializing schema...")
|
||
await init_schema(engine)
|
||
try:
|
||
await init_timescaledb(engine)
|
||
print("Schema and TimescaleDB initialized.")
|
||
except Exception as e:
|
||
print(f"TimescaleDB init warning (might already exist): {e}")
|
||
|
||
# 1. 准备测试数据
|
||
total_records = int(os.getenv("SMARTEDT_TEST_RECORDS", "300000"))
|
||
batch_size = int(os.getenv("SMARTEDT_TEST_BATCH_SIZE", "1000"))
|
||
simulation_id = f"TEST_SIM_{int(time.time())}"
|
||
device_id = "test_device_01"
|
||
|
||
print(f"Generating {total_records} records for simulation {simulation_id}...")
|
||
|
||
print("Starting insertion test...")
|
||
|
||
# 2. 插入性能测试
|
||
insert_start_time = time.time()
|
||
|
||
async with engine.begin() as conn:
|
||
# 分批插入
|
||
for base_seq in range(0, total_records, batch_size):
|
||
batch = []
|
||
end_seq = min(base_seq + batch_size, total_records)
|
||
for seq in range(base_seq, end_seq):
|
||
batch.append(
|
||
{
|
||
"ts": datetime.now(timezone.utc),
|
||
"simulation_id": simulation_id,
|
||
"device_id": device_id,
|
||
"seq": seq,
|
||
"signals": generate_payload(),
|
||
}
|
||
)
|
||
await conn.execute(insert(vehicle_signals), batch)
|
||
if end_seq % 50000 == 0:
|
||
print(f"Inserted {end_seq} records...")
|
||
|
||
insert_end_time = time.time()
|
||
insert_duration = insert_end_time - insert_start_time
|
||
print(f"\n✅ Insertion Test Complete:")
|
||
print(f"Total Records: {total_records}")
|
||
print(f"Time Taken: {insert_duration:.4f} seconds")
|
||
print(f"Throughput: {total_records / insert_duration:.2f} records/sec")
|
||
|
||
# 3. 查询性能测试
|
||
print("\nStarting query performance test...")
|
||
|
||
# 3.1 简单计数查询
|
||
query_start = time.time()
|
||
async with engine.connect() as conn:
|
||
result = await conn.execute(
|
||
select(text("count(*)")).select_from(vehicle_signals).where(vehicle_signals.c.simulation_id == simulation_id)
|
||
)
|
||
count = result.scalar()
|
||
query_end = time.time()
|
||
print(f"Query 1 (Count): Found {count} records in {query_end - query_start:.4f} seconds")
|
||
|
||
# 3.2 复杂 JSONB 查询 (查询车速 > 100 的记录数)
|
||
# 注意:JSONB 查询语法取决于数据库和 SQLAlchemy 版本,这里使用 text() 以确保兼容性
|
||
query_start = time.time()
|
||
async with engine.connect() as conn:
|
||
# 查询 signals->>'vehicle_speed_kmh' > 100
|
||
stmt = text(
|
||
"SELECT count(*) FROM vehicle_signals "
|
||
"WHERE simulation_id = :sim_id "
|
||
"AND (signals->>'vehicle_speed_kmh')::float > 100"
|
||
)
|
||
result = await conn.execute(stmt, {"sim_id": simulation_id})
|
||
high_speed_count = result.scalar()
|
||
query_end = time.time()
|
||
print(f"Query 2 (JSONB Filter): Found {high_speed_count} records with speed > 100 in {query_end - query_start:.4f} seconds")
|
||
|
||
# 3.3 时间范围查询 (查询最近 1000 条)
|
||
query_start = time.time()
|
||
async with engine.connect() as conn:
|
||
stmt = (
|
||
select(vehicle_signals)
|
||
.where(vehicle_signals.c.simulation_id == simulation_id)
|
||
.order_by(vehicle_signals.c.ts.desc())
|
||
.limit(1000)
|
||
)
|
||
result = await conn.execute(stmt)
|
||
rows = result.fetchall()
|
||
query_end = time.time()
|
||
print(f"Query 3 (Time Range Limit): Retrieved {len(rows)} records in {query_end - query_start:.4f} seconds")
|
||
|
||
await engine.dispose()
|
||
|
||
if __name__ == "__main__":
|
||
# 确保在 Windows 上正确运行 asyncio
|
||
if hasattr(asyncio, 'WindowsSelectorEventLoopPolicy'):
|
||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||
|
||
asyncio.run(run_test())
|