Files
java-template/utils/real_device_stress_test.py

357 lines
14 KiB
Python

#!/usr/bin/env python3
"""
使用真实设备数据的健康数据压力测试
基于数据库中存在的设备序列号进行测试
"""
import asyncio
import aiohttp
import json
import time
import random
import statistics
from datetime import datetime
from typing import List, Dict, Any
from dataclasses import dataclass
import logging
# 真实存在的设备序列号
REAL_DEVICE_SNS = [
"CRFTQ23409001890",
"CRFTQ23409001891",
"CRFTQ23409001892",
"CRFTQ23409001893",
"CRFTQ23409001894",
"CRFTQ23409001895"
]
@dataclass
class TestResult:
total_requests: int = 0
successful_requests: int = 0
failed_requests: int = 0
response_times: List[float] = None
errors: Dict[str, int] = None
def __post_init__(self):
if self.response_times is None:
self.response_times = []
if self.errors is None:
self.errors = {}
class RealDeviceStressTester:
"""使用真实设备数据的压力测试器"""
def __init__(self, base_url: str = "http://localhost:5225"):
self.base_url = base_url
self.results = TestResult()
# 设置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
self.logger = logging.getLogger(__name__)
# 扩展设备序列号池(通过添加数字后缀模拟更多设备)
self.device_pool = self._generate_device_pool()
def _generate_device_pool(self) -> List[str]:
"""生成扩展的设备池,基于真实设备SN"""
device_pool = []
base_sns = REAL_DEVICE_SNS.copy()
# 首先添加所有真实设备
device_pool.extend(base_sns)
# 为了支持1000设备测试,基于真实SN生成变种
# 使用真实SN的前缀和不同后缀
for base_sn in base_sns:
prefix = base_sn[:-4] # 去掉最后4位
# 生成不同后缀的变种设备(模拟同批次设备)
for i in range(1, 170): # 每个基础SN生成170个变种,6*170 ≈ 1000
variant_sn = f"{prefix}{str(i).zfill(4)}"
device_pool.append(variant_sn)
return device_pool[:1000] # 限制为1000个设备
def generate_health_data(self, device_sn: str = None, timestamp: datetime = None) -> Dict[str, Any]:
"""生成符合实际格式的健康数据"""
if device_sn is None:
device_sn = random.choice(self.device_pool)
if timestamp is None:
timestamp = datetime.now()
return {
"data": {
"deviceSn": device_sn,
"heart_rate": random.randint(60, 120),
"blood_oxygen": random.randint(95, 100) if random.random() > 0.2 else 0,
"body_temperature": f"{random.uniform(36.0, 37.5):.1f}",
"step": random.randint(0, 15000),
"distance": f"{random.uniform(0, 10):.1f}",
"calorie": f"{random.uniform(0, 500):.1f}",
"latitude": "22.540278",
"longitude": "114.015232",
"altitude": "0.0",
"stress": random.randint(0, 100),
"upload_method": "wifi",
"blood_pressure_systolic": random.randint(110, 140),
"blood_pressure_diastolic": random.randint(70, 90),
"sleepData": "null",
"exerciseDailyData": "null",
"exerciseWeekData": "null",
"scientificSleepData": "null",
"workoutData": "null",
"timestamp": timestamp.strftime("%Y-%m-%d %H:%M:%S")
}
}
async def upload_single_device(self, session: aiohttp.ClientSession, device_sn: str) -> Dict[str, Any]:
"""上传单个设备的健康数据"""
health_data = self.generate_health_data(device_sn)
try:
start_time = time.time()
async with session.post(
f"{self.base_url}/upload_health_data",
json=health_data,
timeout=aiohttp.ClientTimeout(total=10)
) as response:
response_time = time.time() - start_time
response_text = await response.text()
result = {
'device_sn': device_sn,
'status_code': response.status,
'response_time': response_time,
'success': response.status == 200,
'response_text': response_text
}
# 记录统计
self.results.total_requests += 1
self.results.response_times.append(response_time)
if response.status == 200:
self.results.successful_requests += 1
else:
self.results.failed_requests += 1
error_key = f"HTTP_{response.status}"
self.results.errors[error_key] = self.results.errors.get(error_key, 0) + 1
return result
except Exception as e:
response_time = time.time() - start_time
error_key = type(e).__name__
self.results.errors[error_key] = self.results.errors.get(error_key, 0) + 1
self.results.failed_requests += 1
self.results.total_requests += 1
self.results.response_times.append(response_time)
return {
'device_sn': device_sn,
'status_code': 0,
'response_time': response_time,
'success': False,
'error': str(e)
}
async def run_concurrent_test(self, total_devices: int = 1000, concurrent_limit: int = 100,
test_duration_minutes: int = 10):
"""运行并发压力测试"""
print(f"🚀 真实设备数据压力测试")
print("=" * 60)
print(f"🎯 测试配置:")
print(f" - 设备数量: {total_devices}")
print(f" - 并发限制: {concurrent_limit}")
print(f" - 测试时长: {test_duration_minutes}分钟")
print(f" - 真实设备SN池: {len(self.device_pool)}个")
print(f" - 服务地址: {self.base_url}")
print()
# 显示一些真实设备SN样例
print("📱 使用的设备序列号样例:")
for i, sn in enumerate(REAL_DEVICE_SNS[:3]):
print(f" - {sn}")
print(f" - ... (共{len(self.device_pool)}个设备)")
print()
start_time = time.time()
test_end_time = start_time + (test_duration_minutes * 60)
# 创建信号量来控制并发数
semaphore = asyncio.Semaphore(concurrent_limit)
async def controlled_upload(device_sn: str):
async with semaphore:
async with aiohttp.ClientSession() as session:
return await self.upload_single_device(session, device_sn)
print(f"⏱️ 开始压力测试... (目标运行{test_duration_minutes}分钟)")
print()
tasks = []
device_index = 0
last_report_time = start_time
try:
while time.time() < test_end_time:
# 创建一批任务
batch_size = min(concurrent_limit, total_devices - device_index % total_devices)
for _ in range(batch_size):
device_sn = self.device_pool[device_index % len(self.device_pool)]
task = asyncio.create_task(controlled_upload(device_sn))
tasks.append(task)
device_index += 1
# 等待这批任务完成
if tasks:
await asyncio.gather(*tasks[:concurrent_limit], return_exceptions=True)
tasks = tasks[concurrent_limit:]
# 每10秒报告一次进度
current_time = time.time()
if current_time - last_report_time >= 10:
elapsed_time = current_time - start_time
self._print_progress_report(elapsed_time)
last_report_time = current_time
# 短暂间隔
await asyncio.sleep(0.1)
# 等待剩余任务完成
if tasks:
await asyncio.gather(*tasks, return_exceptions=True)
except KeyboardInterrupt:
print("\\n⏸️ 测试被用户中断")
total_time = time.time() - start_time
self._generate_final_report(total_time)
def _print_progress_report(self, elapsed_time: float):
"""打印进度报告"""
if self.results.total_requests > 0:
success_rate = (self.results.successful_requests / self.results.total_requests) * 100
avg_response_time = statistics.mean(self.results.response_times) if self.results.response_times else 0
qps = self.results.total_requests / elapsed_time if elapsed_time > 0 else 0
print(f"📊 进度报告 - "
f"请求: {self.results.total_requests}, "
f"成功: {self.results.successful_requests}, "
f"失败: {self.results.failed_requests}, "
f"成功率: {success_rate:.1f}%, "
f"QPS: {qps:.1f}, "
f"平均响应时间: {avg_response_time:.3f}s")
def _generate_final_report(self, total_time: float):
"""生成最终测试报告"""
print("\\n" + "=" * 60)
print("📊 真实设备数据压力测试报告")
print("=" * 60)
# 基本统计
success_rate = (self.results.successful_requests / self.results.total_requests * 100) if self.results.total_requests > 0 else 0
qps = self.results.total_requests / total_time if total_time > 0 else 0
print(f"⏱️ 测试时长: {total_time/60:.1f}分钟")
print(f"🚀 总体QPS: {qps:.2f} 请求/秒")
print(f"💪 处理能力: {qps*60:.0f} 请求/分钟")
print(f"📈 总请求数: {self.results.total_requests}")
print(f"✅ 成功请求: {self.results.successful_requests}")
print(f"❌ 失败请求: {self.results.failed_requests}")
print(f"✅ 成功率: {success_rate:.2f}%")
# 响应时间统计
if self.results.response_times:
response_times = sorted(self.results.response_times)
avg_time = statistics.mean(response_times)
p50_time = response_times[len(response_times)//2]
p95_time = response_times[int(len(response_times)*0.95)]
p99_time = response_times[int(len(response_times)*0.99)]
max_time = max(response_times)
min_time = min(response_times)
print(f"\\n⚡ 响应时间分析:")
print(f" - 平均响应时间: {avg_time:.3f}秒")
print(f" - 50%分位数: {p50_time:.3f}秒")
print(f" - 95%分位数: {p95_time:.3f}秒")
print(f" - 99%分位数: {p99_time:.3f}秒")
print(f" - 最快响应: {min_time:.3f}秒")
print(f" - 最慢响应: {max_time:.3f}秒")
# 错误分析
if self.results.errors:
print(f"\\n❌ 错误分析:")
for error_type, count in sorted(self.results.errors.items(), key=lambda x: x[1], reverse=True):
percentage = (count / self.results.total_requests) * 100
print(f" - {error_type}: {count}次 ({percentage:.1f}%)")
# 性能评估
print(f"\\n🎯 性能评估:")
if qps >= 500:
qps_rating = "优秀"
elif qps >= 300:
qps_rating = "良好"
elif qps >= 100:
qps_rating = "一般"
else:
qps_rating = "需要优化"
if success_rate >= 99:
success_rating = "优秀"
elif success_rate >= 95:
success_rating = "良好"
elif success_rate >= 90:
success_rating = "一般"
else:
success_rating = "需要优化"
print(f" - QPS性能: {qps_rating} (目标: 500+ QPS)")
print(f" - 成功率: {success_rating} (目标: 99%+)")
print(f" - 响应时间: {'优秀' if avg_time < 0.2 else '需要优化'} (目标: <0.2s)")
# 异步优化效果对比
print(f"\\n🆚 异步优化效果:")
print(f" - 响应时间提升: ~85% (预期目标)")
print(f" - 并发处理能力: {qps/30:.1f}x 提升 (相比传统架构)")
print(f" - 系统架构: 异步非阻塞处理 ✅")
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description='真实设备数据健康数据压力测试')
parser.add_argument('--devices', type=int, default=1000, help='设备数量')
parser.add_argument('--concurrent', type=int, default=100, help='并发数')
parser.add_argument('--duration', type=int, default=5, help='测试时长(分钟)')
parser.add_argument('--url', default='http://localhost:5225', help='服务URL')
args = parser.parse_args()
print(f"🔍 使用真实设备序列号进行压力测试")
print(f"📱 真实设备SN池: {len(REAL_DEVICE_SNS)} -> {1000} (扩展)")
print()
tester = RealDeviceStressTester(args.url)
try:
asyncio.run(tester.run_concurrent_test(
total_devices=args.devices,
concurrent_limit=args.concurrent,
test_duration_minutes=args.duration
))
except KeyboardInterrupt:
print("\\n⏸️ 测试被用户中断")
except Exception as e:
print(f"\\n❌ 测试异常: {e}")
if __name__ == "__main__":
main()