75 files added
1 files deleted
| New file |
| | |
| | | # 默认忽略的文件 |
| | | /shelf/ |
| | | /workspace.xml |
| | | # Editor-based HTTP Client requests |
| | | /httpRequests/ |
| | | # Datasource local storage ignored files |
| | | /dataSources/ |
| | | /dataSources.local.xml |
| New file |
| | |
| | | <component name="InspectionProjectProfileManager"> |
| | | <settings> |
| | | <option name="USE_PROJECT_PROFILE" value="false" /> |
| | | <version value="1.0" /> |
| | | </settings> |
| | | </component> |
| New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project version="4"> |
| | | <component name="Black"> |
| | | <option name="sdkName" value="Python 3.12 (造数脚本)" /> |
| | | </component> |
| | | <component name="ProjectRootManager" version="2" project-jdk-name="G:\Anaconda3" project-jdk-type="Python SDK" /> |
| | | <component name="PythonCompatibilityInspectionAdvertiser"> |
| | | <option name="version" value="3" /> |
| | | </component> |
| | | </project> |
| New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project version="4"> |
| | | <component name="ProjectModuleManager"> |
| | | <modules> |
| | | <module fileurl="file://$PROJECT_DIR$/.idea/造数脚本.iml" filepath="$PROJECT_DIR$/.idea/造数脚本.iml" /> |
| | | </modules> |
| | | </component> |
| | | </project> |
| New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project version="4"> |
| | | <component name="VcsDirectoryMappings"> |
| | | <mapping directory="" vcs="Git" /> |
| | | </component> |
| | | </project> |
| New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <module type="PYTHON_MODULE" version="4"> |
| | | <component name="NewModuleRootManager"> |
| | | <content url="file://$MODULE_DIR$"> |
| | | <excludeFolder url="file://$MODULE_DIR$/.venv" /> |
| | | </content> |
| | | <orderEntry type="jdk" jdkName="G:\Anaconda3" jdkType="Python SDK" /> |
| | | <orderEntry type="sourceFolder" forTests="false" /> |
| | | </component> |
| | | </module> |
| New file |
| | |
| | | import time |
| | | import hmac |
| | | import hashlib |
| | | import base64 |
| | | import urllib.parse |
| | | import requests |
| | | import json |
| | | |
| | | class DingTalkHelper: |
| | | def __init__(self, access_token, secret): |
| | | self.access_token = access_token |
| | | self.secret = secret |
| | | |
| | | def calculate_sign(self): |
| | | # 获取当前时间戳,单位为毫秒 |
| | | timestamp = str(round(time.time() * 1000)) |
| | | |
| | | # 使用 HMAC SHA256 加密算法生成签名 |
| | | string_to_sign = '{}\n{}'.format(timestamp, self.secret) |
| | | string_to_sign_enc = string_to_sign.encode('utf-8') |
| | | secret_enc = self.secret.encode('utf-8') |
| | | |
| | | # 计算 HMAC 值 |
| | | hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest() |
| | | |
| | | # 使用 base64 编码并对 URL 进行编码 |
| | | sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) |
| | | |
| | | return timestamp, sign |
| | | |
| | | def send_markdown(self, title, text, is_at_all=False): |
| | | timestamp, sign = self.calculate_sign() |
| | | dingtalk_webhook = f'https://oapi.dingtalk.com/robot/send?access_token={self.access_token}×tamp={timestamp}&sign={sign}' |
| | | |
| | | payload = { |
| | | "msgtype": "markdown", |
| | | "markdown": { |
| | | "title": title, |
| | | "text": text |
| | | }, |
| | | "at": { |
| | | "isAtAll": is_at_all |
| | | } |
| | | } |
| | | |
| | | try: |
| | | response = requests.post(dingtalk_webhook, json=payload) |
| | | if response.status_code == 200: |
| | | print("Markdown消息发送成功!") |
| | | else: |
| | | print(f"发送失败,状态码:{response.status_code}") |
| | | except Exception as e: |
| | | print(f"发送异常:{e}") |
| | | |
| | | def send_message(self, message): |
| | | # 计算签名和时间戳 |
| | | timestamp, sign = self.calculate_sign() |
| | | |
| | | # 构建钉钉 Webhook URL,拼接上 timestamp 和 sign |
| | | dingtalk_webhook = f'https://oapi.dingtalk.com/robot/send?access_token={self.access_token}×tamp={timestamp}&sign={sign}' |
| | | |
| | | headers = { |
| | | 'Content-Type': 'application/json', |
| | | } |
| | | |
| | | # 构建发送的消息内容 |
| | | payload = { |
| | | "msgtype": "text", |
| | | "text": { |
| | | "content": message |
| | | } |
| | | } |
| | | |
| | | try: |
| | | # 发送请求 |
| | | response = requests.post(dingtalk_webhook, json=payload, headers=headers) |
| | | |
| | | # 输出返回的状态码和响应内容 |
| | | print(f"Response status: {response.status_code}") |
| | | print(f"Response text: {response.text}") |
| | | |
| | | if response.status_code == 200: |
| | | print("Message sent successfully!") |
| | | else: |
| | | print("Failed to send message.") |
| | | except Exception as e: |
| | | print(f"Error occurred: {e}") |
| New file |
| | |
| | | import random |
| | | from datetime import datetime, timedelta |
| | | class RandomUtil: |
| | | @staticmethod |
| | | # 随机生成数字字符串 |
| | | def generate_random_number_string(start: str, end: str) -> str: |
| | | """ |
| | | 在指定范围内生成一个随机数字字符串。 |
| | | |
| | | :param start: 范围起始值(包含),数字字符串形式 |
| | | :param end: 范围终止值(包含),数字字符串形式 |
| | | :return: 生成的随机数字字符串 |
| | | :raises ValueError: 如果输入的字符串不能转换为数字或者起始值大于终止值 |
| | | """ |
| | | try: |
| | | start_num = int(start) |
| | | end_num = int(end) |
| | | except ValueError as e: |
| | | raise ValueError("输入的字符串必须为有效的数字") from e |
| | | |
| | | if start_num > end_num: |
| | | raise ValueError("起始值不能大于终止值") |
| | | |
| | | # 使用 random.randint 生成包含 start_num 和 end_num 的随机数 |
| | | random_number = random.randint(start_num, end_num) |
| | | return str(random_number) |
| | | |
| | | # 随机生成年月日 |
| | | def generate_random_date(start_date: str, end_date: str) -> str: |
| | | """ |
| | | 在指定的年月日范围内随机生成一个日期字符串,格式为 %Y-%m-%d。 |
| | | |
| | | :param start_date: 起始日期字符串,格式为 "YYYY-MM-DD" |
| | | :param end_date: 结束日期字符串,格式为 "YYYY-MM-DD" |
| | | :return: 随机生成的日期字符串 |
| | | :raises ValueError: 如果日期格式不正确或者起始日期大于结束日期 |
| | | """ |
| | | try: |
| | | start = datetime.strptime(start_date, "%Y-%m-%d") |
| | | end = datetime.strptime(end_date, "%Y-%m-%d") |
| | | except ValueError as e: |
| | | raise ValueError("输入的日期字符串必须为 'YYYY-MM-DD' 格式") from e |
| | | |
| | | if start > end: |
| | | raise ValueError("起始日期不能大于结束日期") |
| | | |
| | | # 计算起始和结束日期之间的天数差 |
| | | delta_days = (end - start).days |
| | | |
| | | # 随机选择一个偏移天数 |
| | | random_days = random.randint(0, delta_days) |
| | | |
| | | # 计算随机日期 |
| | | random_date = start + timedelta(days=random_days) |
| | | return random_date.strftime("%Y-%m-%d") |
| | | |
| | | # 示例用法 |
| | | if __name__ == '__main__': |
| | | start = "1000" |
| | | end = "9999" |
| | | random_str = RandomUtil.generate_random_number_string(start, end) |
| | | print("随机生成的数字字符串为:", random_str) |
| New file |
| | |
| | | """ |
| | | 压测报告生成器(中文报告) |
| | | |
| | | 依赖: |
| | | - numpy |
| | | - pandas (可选,用于更方便的表格导出) |
| | | - matplotlib (用于绘图) |
| | | - python-docx (可选,用于生成 Word 文档) |
| | | |
| | | 使用场景:在你的压测脚本中,逐条调用 `recorder.record_result(...)` 或者在压测结束后把结果一次性传入 `bulk_record`, |
| | | 然后调用 `generate_report(output_dir, formats=['html','docx','json','csv'])`。 |
| | | |
| | | 输出:HTML 报告(含统计与图表)、可选的 Word 报告、JSON/CSV 明细文件和图像文件。 |
| | | |
| | | """ |
| | | |
| | | from __future__ import annotations |
| | | import os |
| | | import json |
| | | import math |
| | | import time |
| | | import statistics |
| | | import datetime |
| | | from typing import List, Optional, Dict, Any |
| | | import matplotlib.pyplot as plt |
| | | plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文字体为黑体 |
| | | plt.rcParams['axes.unicode_minus'] = False # 正常显示负号 |
| | | |
| | | try: |
| | | import numpy as np |
| | | except Exception: |
| | | np = None |
| | | |
| | | try: |
| | | import pandas as pd |
| | | except Exception: |
| | | pd = None |
| | | |
| | | try: |
| | | import matplotlib |
| | | matplotlib.use('Agg') |
| | | import matplotlib.pyplot as plt |
| | | except Exception: |
| | | plt = None |
| | | |
| | | try: |
| | | from docx import Document |
| | | from docx.shared import Inches |
| | | except Exception: |
| | | Document = None |
| | | |
| | | |
| | | class RequestRecord: |
| | | """单条请求记录数据结构。""" |
| | | |
| | | def __init__(self, index: int, timestamp: float, status_code: int, latency_ms: float, |
| | | response_size: Optional[int] = None, error: Optional[str] = None, extra: Optional[Dict] = None): |
| | | self.index = index |
| | | self.timestamp = timestamp # unix 时间戳,秒 |
| | | self.status_code = status_code |
| | | self.latency_ms = latency_ms |
| | | self.response_size = response_size |
| | | self.error = error |
| | | self.extra = extra or {} |
| | | |
| | | def to_dict(self) -> Dict[str, Any]: |
| | | return { |
| | | 'index': self.index, |
| | | 'timestamp': self.timestamp, |
| | | 'datetime': datetime.datetime.fromtimestamp(self.timestamp).isoformat(sep=' ', timespec='seconds'), |
| | | 'status_code': self.status_code, |
| | | 'latency_ms': self.latency_ms, |
| | | 'response_size': self.response_size, |
| | | 'error': self.error, |
| | | **(self.extra or {}) |
| | | } |
| | | |
| | | |
| | | class LoadTestReportGenerator: |
| | | """压测报告生成器。将请求结果记录并生成中文报告。""" |
| | | |
| | | def __init__(self, test_name: str = '压测任务', report_title: Optional[str] = None): |
| | | self.test_name = test_name |
| | | self.report_title = report_title or f"{test_name} 报告" |
| | | self.records: List[RequestRecord] = [] |
| | | self._started_at: Optional[float] = None |
| | | self._ended_at: Optional[float] = None |
| | | |
| | | # ---------- 记录方法 ---------- |
| | | def record_result(self, index: int, timestamp: float, status_code: int, latency_ms: float, |
| | | response_size: Optional[int] = None, error: Optional[str] = None, extra: Optional[Dict] = None): |
| | | """记录单条请求结果。timestamp 为 unix 时间戳(秒)。""" |
| | | rec = RequestRecord(index, timestamp, status_code, latency_ms, response_size, error, extra) |
| | | self.records.append(rec) |
| | | if self._started_at is None or timestamp < self._started_at: |
| | | self._started_at = timestamp |
| | | if self._ended_at is None or timestamp > self._ended_at: |
| | | self._ended_at = timestamp |
| | | |
| | | def bulk_record(self, results: List[Dict[str, Any]]): |
| | | """一次性批量导入结果。每一项是 dict,需包含 index,timestamp,status_code,latency_ms 等字段。""" |
| | | for r in results: |
| | | self.record_result( |
| | | index=r.get('index', len(self.records) + 1), |
| | | timestamp=r['timestamp'], |
| | | status_code=int(r.get('status_code', 0)), |
| | | latency_ms=float(r.get('latency_ms', 0)), |
| | | response_size=r.get('response_size'), |
| | | error=r.get('error'), |
| | | extra=r.get('extra') |
| | | ) |
| | | |
| | | # ---------- 统计方法 ---------- |
| | | def compute_stats(self) -> Dict[str, Any]: |
| | | if not self.records: |
| | | return {} |
| | | |
| | | latencies = [r.latency_ms for r in self.records if r.latency_ms is not None] |
| | | statuses = [r.status_code for r in self.records] |
| | | errors = [r for r in self.records if r.error] |
| | | |
| | | total = len(self.records) |
| | | success_count = sum(1 for s in statuses if 200 <= s < 300) |
| | | fail_count = total - success_count |
| | | |
| | | duration = (self._ended_at - self._started_at) if (self._started_at and self._ended_at and self._ended_at > self._started_at) else None |
| | | duration = float(duration) if duration else 0.0 |
| | | |
| | | throughput = (total / duration) if duration > 0 else 0.0 |
| | | |
| | | # 使用 numpy 计算分位数(如果可用),否则用纯 python |
| | | if np: |
| | | p50 = float(np.percentile(latencies, 50)) if latencies else 0 |
| | | p90 = float(np.percentile(latencies, 90)) if latencies else 0 |
| | | p95 = float(np.percentile(latencies, 95)) if latencies else 0 |
| | | p99 = float(np.percentile(latencies, 99)) if latencies else 0 |
| | | else: |
| | | lat_sorted = sorted(latencies) |
| | | def percentile(lst, q): |
| | | if not lst: |
| | | return 0 |
| | | k = (len(lst)-1) * (q/100) |
| | | f = math.floor(k) |
| | | c = math.ceil(k) |
| | | if f == c: |
| | | return lst[int(k)] |
| | | d0 = lst[int(f)] * (c-k) |
| | | d1 = lst[int(c)] * (k-f) |
| | | return d0 + d1 |
| | | p50 = percentile(lat_sorted, 50) |
| | | p90 = percentile(lat_sorted, 90) |
| | | p95 = percentile(lat_sorted, 95) |
| | | p99 = percentile(lat_sorted, 99) |
| | | |
| | | status_groups: Dict[int, int] = {} |
| | | for s in statuses: |
| | | status_groups[s] = status_groups.get(s, 0) + 1 |
| | | |
| | | # 每秒请求数(RPS)时间序列 |
| | | rps_series = {} |
| | | for r in self.records: |
| | | sec = int(r.timestamp) |
| | | rps_series[sec] = rps_series.get(sec, 0) + 1 |
| | | |
| | | # 简单错误聚合 |
| | | error_summary: Dict[str, int] = {} |
| | | for r in errors: |
| | | key = r.error if r.error else f'status_{r.status_code}' |
| | | error_summary[key] = error_summary.get(key, 0) + 1 |
| | | |
| | | stats = { |
| | | 'total_requests': total, |
| | | 'success_count': success_count, |
| | | 'fail_count': fail_count, |
| | | 'success_rate': success_count / total if total else 0, |
| | | 'duration_seconds': duration, |
| | | 'throughput_rps': throughput, |
| | | 'latency_ms': { |
| | | 'min': min(latencies) if latencies else 0, |
| | | 'max': max(latencies) if latencies else 0, |
| | | 'avg': statistics.mean(latencies) if latencies else 0, |
| | | 'median': p50, |
| | | 'p90': p90, |
| | | 'p95': p95, |
| | | 'p99': p99, |
| | | }, |
| | | 'status_groups': status_groups, |
| | | 'error_summary': error_summary, |
| | | 'rps_series': sorted(list(rps_series.items())), # [(sec, count), ...] |
| | | } |
| | | return stats |
| | | |
| | | # ---------- 报告输出 ---------- |
| | | def _ensure_dir(self, path: str): |
| | | if not os.path.exists(path): |
| | | os.makedirs(path, exist_ok=True) |
| | | |
| | | def generate_report(self, output_dir: str = './load_test_report', formats: Optional[List[str]] = None): |
| | | """ |
| | | 生成报告。 |
| | | formats: 列表,支持 'html','docx','json','csv'。 |
| | | 返回生成的文件路径字典。 |
| | | """ |
| | | formats = formats or ['html', 'json'] |
| | | self._ensure_dir(output_dir) |
| | | |
| | | stats = self.compute_stats() |
| | | timestamp_str = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') |
| | | base_name = f"{self.test_name.replace(' ', '_')}_{timestamp_str}" |
| | | |
| | | outputs = {} |
| | | |
| | | # 1) 输出 JSON 统计与明细 |
| | | if 'json' in formats: |
| | | json_path = os.path.join(output_dir, base_name + '.summary.json') |
| | | with open(json_path, 'w', encoding='utf-8') as f: |
| | | json.dump({'stats': stats, 'records': [r.to_dict() for r in self.records]}, f, ensure_ascii=False, indent=2) |
| | | outputs['json'] = json_path |
| | | |
| | | # 2) 输出 CSV 明细(如果 pandas 可用则用 pandas,否则手写) |
| | | if 'csv' in formats: |
| | | csv_path = os.path.join(output_dir, base_name + '.details.csv') |
| | | if pd: |
| | | df = pd.DataFrame([r.to_dict() for r in self.records]) |
| | | df.to_csv(csv_path, index=False, encoding='utf-8-sig') |
| | | else: |
| | | # 手动写入 |
| | | import csv |
| | | with open(csv_path, 'w', newline='', encoding='utf-8') as f: |
| | | writer = csv.DictWriter(f, fieldnames=list(self.records[0].to_dict().keys())) |
| | | writer.writeheader() |
| | | for r in self.records: |
| | | writer.writerow(r.to_dict()) |
| | | outputs['csv'] = csv_path |
| | | |
| | | # 3) 生成图表(延迟分布与RPS),保存为 PNG |
| | | charts = {} |
| | | if plt: |
| | | try: |
| | | # 延迟直方图 |
| | | latencies = [r.latency_ms for r in self.records if r.latency_ms is not None] |
| | | if latencies: |
| | | plt.figure() |
| | | plt.hist(latencies, bins=50) |
| | | plt.title('响应时间分布 (ms)') |
| | | plt.xlabel('延迟 (ms)') |
| | | plt.ylabel('请求数量') |
| | | hist_path = os.path.join(output_dir, base_name + '_latency_hist.png') |
| | | plt.tight_layout() |
| | | plt.savefig(hist_path) |
| | | plt.close() |
| | | charts['latency_hist'] = hist_path |
| | | |
| | | # RPS 图 |
| | | times = [int(r.timestamp) for r in self.records] |
| | | if times: |
| | | from collections import Counter |
| | | cnt = Counter(times) |
| | | xs = sorted(cnt.keys()) |
| | | ys = [cnt[x] for x in xs] |
| | | plt.figure() |
| | | plt.plot(xs, ys) |
| | | plt.title('每秒请求数 (RPS)') |
| | | plt.xlabel('Unix 秒') |
| | | plt.ylabel('请求数') |
| | | rps_path = os.path.join(output_dir, base_name + '_rps.png') |
| | | plt.tight_layout() |
| | | plt.savefig(rps_path) |
| | | plt.close() |
| | | charts['rps'] = rps_path |
| | | except Exception as e: |
| | | print('生成图表时出错:', e) |
| | | |
| | | outputs['charts'] = charts |
| | | |
| | | # 4) 生成 HTML 报告 |
| | | if 'html' in formats: |
| | | html_path = os.path.join(output_dir, base_name + '.html') |
| | | html_content = self._build_html_report(stats, charts) |
| | | with open(html_path, 'w', encoding='utf-8') as f: |
| | | f.write(html_content) |
| | | outputs['html'] = html_path |
| | | |
| | | # 5) 生成 Word 报告(可选) |
| | | if 'docx' in formats and Document: |
| | | try: |
| | | docx_path = os.path.join(output_dir, base_name + '.docx') |
| | | self._build_docx_report(stats, charts, docx_path) |
| | | outputs['docx'] = docx_path |
| | | except Exception as e: |
| | | print('生成 docx 报告时出错:', e) |
| | | |
| | | return outputs |
| | | |
| | | def _build_html_report(self, stats: Dict[str, Any], charts: Dict[str, str]) -> str: |
| | | # 这里构建一份中文的 HTML 模板(简洁风格) |
| | | title = self.report_title |
| | | now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
| | | summary_html = f""" |
| | | <h2>摘要</h2> |
| | | <ul> |
| | | <li>报告名称:{title}</li> |
| | | <li>生成时间:{now}</li> |
| | | <li>总请求数:{stats.get('total_requests', 0)}</li> |
| | | <li>成功数:{stats.get('success_count',0)},失败数:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}</li> |
| | | <li>总耗时(秒):{stats.get('duration_seconds',0):.2f}</li> |
| | | <li>平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}</li> |
| | | </ul> |
| | | """ |
| | | |
| | | latency = stats.get('latency_ms', {}) |
| | | latency_html = f""" |
| | | <h2>响应时间统计 (ms)</h2> |
| | | <ul> |
| | | <li>最小:{latency.get('min',0):.2f}</li> |
| | | <li>最大:{latency.get('max',0):.2f}</li> |
| | | <li>平均:{latency.get('avg',0):.2f}</li> |
| | | <li>中位数(P50):{latency.get('median',0):.2f}</li> |
| | | <li>P90:{latency.get('p90',0):.2f},P95:{latency.get('p95',0):.2f},P99:{latency.get('p99',0):.2f}</li> |
| | | </ul> |
| | | """ |
| | | |
| | | status_html = '<h2>状态码分布</h2><ul>' |
| | | for k, v in stats.get('status_groups', {}).items(): |
| | | status_html += f'<li>{k}: {v}</li>' |
| | | status_html += '</ul>' |
| | | |
| | | error_html = '<h2>错误汇总</h2>' |
| | | if stats.get('error_summary'): |
| | | error_html += '<ul>' |
| | | for k, v in stats.get('error_summary', {}).items(): |
| | | error_html += f'<li>{k}: {v}</li>' |
| | | error_html += '</ul>' |
| | | else: |
| | | error_html += '<p>无错误记录</p>' |
| | | |
| | | charts_html = '<h2>图表</h2>' |
| | | for name, path in charts.items(): |
| | | charts_html += f'<div><h3>{name}</h3><img src="{os.path.basename(path)}" alt="{name}" style="max-width:100%;height:auto;"/></div>' |
| | | |
| | | # 详细请求表(默认仅包含前 100 条,避免页面过大) |
| | | details = [r.to_dict() for r in self.records[:100]] |
| | | detail_rows = ''.join([f"<tr><td>{d['index']}</td><td>{d['datetime']}</td><td>{d['status_code']}</td><td>{d['latency_ms']}</td><td>{d['response_size']}</td><td>{d['error'] or ''}</td></tr>" for d in details]) |
| | | details_html = f""" |
| | | <h2>请求明细(仅显示前100条)</h2> |
| | | <table border="1" cellpadding="4" cellspacing="0"> |
| | | <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr> |
| | | {detail_rows} |
| | | </table> |
| | | """ |
| | | |
| | | html = f""" |
| | | <!doctype html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="utf-8"> |
| | | <title>{title}</title> |
| | | <style> |
| | | body{{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px}} |
| | | h2{{color:#2c3e50}} |
| | | table{{border-collapse:collapse; width:100%}} |
| | | th,td{{padding:6px; text-align:left}} |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>{title}</h1> |
| | | {summary_html} |
| | | {latency_html} |
| | | {status_html} |
| | | {error_html} |
| | | {charts_html} |
| | | {details_html} |
| | | <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p> |
| | | </body> |
| | | </html> |
| | | """ |
| | | |
| | | # 若有图表,将图表文件拷贝到同目录(图表已保存在 output 目录),HTML 中用相对路径引用 basename |
| | | return html |
| | | |
| | | def _build_docx_report(self, stats: Dict[str, Any], charts: Dict[str, str], docx_path: str): |
| | | if not Document: |
| | | raise RuntimeError('缺少 python-docx 库,无法生成 docx。') |
| | | doc = Document() |
| | | doc.add_heading(self.report_title, level=1) |
| | | doc.add_paragraph(f"生成时间:{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") |
| | | |
| | | doc.add_heading('摘要', level=2) |
| | | doc.add_paragraph(f"总请求数:{stats.get('total_requests',0)} 成功:{stats.get('success_count',0)} 失败:{stats.get('fail_count',0)} 成功率:{stats.get('success_rate',0):.2%}") |
| | | doc.add_paragraph(f"总耗时(秒):{stats.get('duration_seconds',0):.2f} 平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}") |
| | | |
| | | doc.add_heading('响应时间统计 (ms)', level=2) |
| | | lat = stats.get('latency_ms', {}) |
| | | doc.add_paragraph(f"最小:{lat.get('min',0):.2f} 最大:{lat.get('max',0):.2f} 平均:{lat.get('avg',0):.2f}") |
| | | doc.add_paragraph(f"P50:{lat.get('median',0):.2f} P90:{lat.get('p90',0):.2f} P95:{lat.get('p95',0):.2f} P99:{lat.get('p99',0):.2f}") |
| | | |
| | | doc.add_heading('状态码分布', level=2) |
| | | for k, v in stats.get('status_groups', {}).items(): |
| | | doc.add_paragraph(f"{k}: {v}") |
| | | |
| | | doc.add_heading('错误汇总', level=2) |
| | | if stats.get('error_summary'): |
| | | for k, v in stats.get('error_summary', {}).items(): |
| | | doc.add_paragraph(f"{k}: {v}") |
| | | else: |
| | | doc.add_paragraph('无错误记录') |
| | | |
| | | # 插入图表 |
| | | for name, path in charts.items(): |
| | | if os.path.exists(path): |
| | | doc.add_heading(name, level=2) |
| | | try: |
| | | doc.add_picture(path, width=Inches(6)) |
| | | except Exception: |
| | | doc.add_paragraph(f'无法插入图片:{path}') |
| | | |
| | | # 附加前 100 条请求明细 |
| | | doc.add_heading('请求明细(前100条)', level=2) |
| | | table = doc.add_table(rows=1, cols=6) |
| | | hdr_cells = table.rows[0].cells |
| | | hdr_cells[0].text = '#' |
| | | hdr_cells[1].text = '时间' |
| | | hdr_cells[2].text = '状态码' |
| | | hdr_cells[3].text = '延迟(ms)' |
| | | hdr_cells[4].text = '响应大小' |
| | | hdr_cells[5].text = '错误' |
| | | |
| | | for r in self.records[:100]: |
| | | row_cells = table.add_row().cells |
| | | d = r.to_dict() |
| | | row_cells[0].text = str(d['index']) |
| | | row_cells[1].text = d['datetime'] |
| | | row_cells[2].text = str(d['status_code']) |
| | | row_cells[3].text = f"{d['latency_ms']}" |
| | | row_cells[4].text = str(d.get('response_size', '')) |
| | | row_cells[5].text = d.get('error') or '' |
| | | |
| | | doc.save(docx_path) |
| | | |
| | | |
| | | # ---------------- 使用示例 ---------------- |
| | | if __name__ == '__main__': |
| | | # 简单示例:模拟一些请求结果并生成报告 |
| | | gen = LoadTestReportGenerator(test_name='示例压测', report_title='示例压测详细报告') |
| | | now = time.time() |
| | | # 模拟 100 条请求 |
| | | import random |
| | | for i in range(1, 101): |
| | | ts = now + (i // 5) # 每秒 5 个请求(模拟) |
| | | lat = max(1.0, random.gauss(200, 50)) |
| | | status = 200 if random.random() > 0.05 else 500 |
| | | err = None if status == 200 else '500 Internal Server Error' |
| | | gen.record_result(i, ts, status, lat, response_size=random.randint(500, 5000), error=err) |
| | | |
| | | outputs = gen.generate_report('./example_report', formats=['html', 'json', 'csv', 'docx']) |
| | | print('已生成报告:', outputs) |
| New file |
| | |
| | | import aiohttp |
| | | import asyncio |
| | | |
| | | # 定义接口的URL |
| | | url = "http://ecnu.baoyizn.com:9992/zb/a/repertory/receive/receiveManage/save" |
| | | |
| | | # 定义请求头 |
| | | headers = { |
| | | "Host": "ecnu.baoyizn.com:9992", |
| | | "Connection": "keep-alive", |
| | | "Accept": "application/json, text/javascript, */*; q=0.01", |
| | | "X-Requested-With": "XMLHttpRequest", |
| | | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", |
| | | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", |
| | | "Origin": "http://ecnu.baoyizn.com:9992", |
| | | "Referer": "http://ecnu.baoyizn.com:9992/zb/a/repertory/receive/receiveManage/form/add", |
| | | "Accept-Encoding": "gzip, deflate", |
| | | "Accept-Language": "zh-CN,zh;q=0.9", |
| | | "Cookie": "jeeplus.session.id=21356cbeabb24cbfa12e9fc79590f9fe; rememberMe=/40LFf7oBYkAOgAMrLpNQlRYMxYPntgW0FcmV38n0bcNdiWBj5ODV9yDwIWIsKrYBjkOSB+9kStY/q1kq++cqu8VBevU9vQP4k4yGsRshf2f6kLx5Ql8eXFd7yfXrJd7bLEKDu9aL8aFvTpD3UR6TaoXDJfhhvyVQc8+wlq2lLC9ENONb9wfwdDrV4Ge3uNPnimIR/2U1krjvb1hVXu6ZblNPyWDURaaZoWEQ9Sqe+kB1fXpfOevuefPpg8ZfRZ+Jb56+62xBkIdg08H73aMc/d9njFtWNjl34hlWlS6cGwMLGVCGoAOktmrNv+cIUhYEty7vPh3DzfIp9b7h6Y2KkteGbrcjrGabvtZXZ5RyGY8TJtvF0vu3AFLVz9zaeocYHGjK3Bk55bPnTYXXY4ZHhpGdYKcPyobnS8f0tZxt8dYCOdnGDS7169WURj4VTUExWQ5rx9Bkn6VjuD1pEgogGq2SIs0iWrrXhvk+GjeS1ZbtL791zrV6piyWEN59Wj0XdW7h/nHdieY7CUKLWPcfAYK30pmrtghYovEiXNkk/3X9tYayr9yhSgYs0HchfXNbZAb/iRlNTEFY959XhsOnICE6SblY9i11jUXTC6gLeI3zAe4a8N53bk1GIP4f4OqZtt67LXxIpcbTsFKiOLzRHlAedfc076RnlAmPwIiNyYwCmD1ds1uMU+XGwp4zHgj2KOjS8Kp0HwhfejuCQsk/3PCkCWWspOqx48bIe0Jje+edf5UxJbpEgVDbrZ0Sbg8zw1qofD6Lbz3dCtcHS81lBUoAf6X3PL/0pQuJRpRlD/9SSIhv9uRPJeuvUcIQyC3gDlGiaQcgVMXvnBujMy5iLiKlBpmZcgFtIiiQEyVZ+I=; JSESSIONID=273B42D6BADDAC8D20B8F5A7FDB149AD; cna=wc8xH9vzsEcCAXgpk+X/ItGt; tfstk=fZ3omcgjD0rSmU1pEq4Wv8taYA-YJ8aa8pkLpJhe0-yb2Qi-pmqnpx1-wMhKmJDqn8RSp033-YH4U0hLpk8nBXwPv7lUuxDxKbULz0n3xxlzUQMKpxbnw4eKKXO7TJkEKJnJDFhSNyaeBwY9WbTBMzJrE87U3BP3s7JQzyLZNyaeHsQeXFcSIBTFCH4eiEPaayWzLaR005FU8arFziW4hWzULvrz0oPaOWPU4JyqK17UI2ujuLPp2cLnqdsUwu2Zil_h83E1BPlznw7e97qljbyc8wumcB_MZ8-1MJa8F2VodF_7zkmxPlgJ3aDnhxuzAqJvcva81loEMCsYFq0-uu0kIGao0xg0V4WyN7oSd24-JTbni22ub4rDRBaogWzmbjd1D8gmyqrordj_E4GEvomfEKeKXj3zmAJAovZnD4ZsyK7oQgkO0G5rTwN29qSCAuPbiRt9wk24NSAvTIAcf3ZzG7p9iIjCAuPbiRdDiG6Q4SNJB; HttpOnly=true; Secure=true; theme=blue; pageNo=1; pageSize=50" |
| | | } |
| | | |
| | | # 定义请求体模板 |
| | | data_template = { |
| | | "id": "", |
| | | "remainNum": "121", |
| | | "researchGroup.id": "", |
| | | "repertoryUnit": "21", |
| | | "repertoryName": "测试", |
| | | "receiveTime": "2024-08-13", |
| | | "repertoryId": "f3ecf808422e421b8ca473458d6cf540", |
| | | "repertory.name": "测试", |
| | | "apply.id": "b2e8c68f27e243bc99a363bc2f8260d0", |
| | | "apply.name": "管理员", |
| | | "num": "1", |
| | | "remarks": "" |
| | | } |
| | | |
| | | |
| | | # 定义异步发送请求的函数 |
| | | async def send_request(session, data): |
| | | async with session.post(url, headers=headers, data=data) as response: |
| | | status_code = response.status |
| | | response_text = await response.text() |
| | | print(f"Status Code: {status_code}, Response: {response_text}") |
| | | |
| | | |
| | | # 定义主函数 |
| | | async def main(): |
| | | # 定义并发请求的数量 |
| | | batch_size = 1000 |
| | | tasks = [] |
| | | |
| | | async with aiohttp.ClientSession() as session: |
| | | for i in range(batch_size): |
| | | # 创建请求体 |
| | | data = data_template.copy() |
| | | data["num"] = str(i + 1) |
| | | |
| | | # 创建并发请求任务 |
| | | tasks.append(send_request(session, data)) |
| | | |
| | | # 执行所有请求任务 |
| | | await asyncio.gather(*tasks) |
| | | |
| | | |
| | | # 运行主函数 |
| | | if __name__ == "__main__": |
| | | asyncio.run(main()) |
| New file |
| | |
| | | index,timestamp,datetime,status_code,latency_ms,response_size,error |
| | | 1,1766135741.8356595,2025-12-19 17:15:41,200,33098.71220588684,8, |
| New file |
| | |
| | | |
| | | <!doctype html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="utf-8"> |
| | | <title>压测详细报告</title> |
| | | <style> |
| | | body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px} |
| | | h2{color:#2c3e50} |
| | | table{border-collapse:collapse; width:100%} |
| | | th,td{padding:6px; text-align:left} |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>压测详细报告</h1> |
| | | |
| | | <h2>摘要</h2> |
| | | <ul> |
| | | <li>报告名称:压测详细报告</li> |
| | | <li>生成时间:2025-12-19 17:15:42</li> |
| | | <li>总请求数:1</li> |
| | | <li>成功数:1,失败数:0,成功率:100.00%</li> |
| | | <li>总耗时(秒):0.00</li> |
| | | <li>平均吞吐(req/s):0.00</li> |
| | | </ul> |
| | | |
| | | |
| | | <h2>响应时间统计 (ms)</h2> |
| | | <ul> |
| | | <li>最小:33098.71</li> |
| | | <li>最大:33098.71</li> |
| | | <li>平均:33098.71</li> |
| | | <li>中位数(P50):33098.71</li> |
| | | <li>P90:33098.71,P95:33098.71,P99:33098.71</li> |
| | | </ul> |
| | | |
| | | <h2>状态码分布</h2><ul><li>200: 1</li></ul> |
| | | <h2>错误汇总</h2><p>无错误记录</p> |
| | | <h2>图表</h2><div><h3>latency_hist</h3><img src="压测任务_20251219_171541_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="压测任务_20251219_171541_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div> |
| | | |
| | | <h2>请求明细(仅显示前100条)</h2> |
| | | <table border="1" cellpadding="4" cellspacing="0"> |
| | | <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr> |
| | | <tr><td>1</td><td>2025-12-19 17:15:41</td><td>200</td><td>33098.71220588684</td><td>8</td><td></td></tr> |
| | | </table> |
| | | |
| | | <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p> |
| | | </body> |
| | | </html> |
| | | |
| New file |
| | |
| | | { |
| | | "stats": { |
| | | "total_requests": 1, |
| | | "success_count": 1, |
| | | "fail_count": 0, |
| | | "success_rate": 1.0, |
| | | "duration_seconds": 0.0, |
| | | "throughput_rps": 0.0, |
| | | "latency_ms": { |
| | | "min": 33098.71220588684, |
| | | "max": 33098.71220588684, |
| | | "avg": 33098.71220588684, |
| | | "median": 33098.71220588684, |
| | | "p90": 33098.71220588684, |
| | | "p95": 33098.71220588684, |
| | | "p99": 33098.71220588684 |
| | | }, |
| | | "status_groups": { |
| | | "200": 1 |
| | | }, |
| | | "error_summary": {}, |
| | | "rps_series": [ |
| | | [ |
| | | 1766135741, |
| | | 1 |
| | | ] |
| | | ] |
| | | }, |
| | | "records": [ |
| | | { |
| | | "index": 1, |
| | | "timestamp": 1766135741.8356595, |
| | | "datetime": "2025-12-19 17:15:41", |
| | | "status_code": 200, |
| | | "latency_ms": 33098.71220588684, |
| | | "response_size": 8, |
| | | "error": null |
| | | } |
| | | ] |
| | | } |
| New file |
| | |
| | |
|
| | | <!doctype html>
|
| | | <html lang="zh-CN">
|
| | | <head>
|
| | | <meta charset="utf-8">
|
| | | <title>压测详细报告</title>
|
| | | <style>
|
| | | body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px}
|
| | | h2{color:#2c3e50}
|
| | | table{border-collapse:collapse; width:100%}
|
| | | th,td{padding:6px; text-align:left}
|
| | | </style>
|
| | | </head>
|
| | | <body>
|
| | | <h1>压测详细报告</h1>
|
| | | |
| | | <h2>摘要</h2>
|
| | | <ul>
|
| | | <li>报告名称:压测详细报告</li>
|
| | | <li>生成时间:2025-12-20 15:04:46</li>
|
| | | <li>总请求数:40000</li>
|
| | | <li>成功数:98,失败数:39902,成功率:0.24%</li>
|
| | | <li>总耗时(秒):77576.53</li>
|
| | | <li>平均吞吐(req/s):0.52</li>
|
| | | </ul>
|
| | | |
| | | |
| | | <h2>响应时间统计 (ms)</h2>
|
| | | <ul>
|
| | | <li>最小:6501.80</li>
|
| | | <li>最大:60140.01</li>
|
| | | <li>平均:59948.44</li>
|
| | | <li>中位数(P50):60006.91</li>
|
| | | <li>P90:60055.25,P95:60062.55,P99:60068.00</li>
|
| | | </ul>
|
| | | |
| | | <h2>状态码分布</h2><ul><li>200: 98</li><li>0: 39902</li></ul>
|
| | | <h2>错误汇总</h2><ul><li>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | : 38477</li><li>TimeoutError:: 1425</li></ul>
|
| | | <h2>图表</h2><div><h3>latency_hist</h3><img src="压测任务_20251220_150442_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="压测任务_20251220_150442_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div>
|
| | | |
| | | <h2>请求明细(仅显示前100条)</h2>
|
| | | <table border="1" cellpadding="4" cellspacing="0">
|
| | | <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr>
|
| | | <tr><td>47</td><td>2025-12-19 17:31:45</td><td>200</td><td>6501.801252365112</td><td>8</td><td></td></tr><tr><td>35</td><td>2025-12-19 17:31:45</td><td>200</td><td>6536.803960800171</td><td>8</td><td></td></tr><tr><td>31</td><td>2025-12-19 17:31:45</td><td>200</td><td>6587.803363800049</td><td>8</td><td></td></tr><tr><td>80</td><td>2025-12-19 17:31:45</td><td>200</td><td>6601.800441741943</td><td>8</td><td></td></tr><tr><td>4</td><td>2025-12-19 17:31:46</td><td>200</td><td>6997.430086135864</td><td>8</td><td></td></tr><tr><td>21</td><td>2025-12-19 17:31:46</td><td>200</td><td>7473.469257354736</td><td>8</td><td></td></tr><tr><td>98</td><td>2025-12-19 17:31:47</td><td>200</td><td>8192.993879318237</td><td>8</td><td></td></tr><tr><td>37</td><td>2025-12-19 17:31:50</td><td>200</td><td>11399.858713150024</td><td>8</td><td></td></tr><tr><td>96</td><td>2025-12-19 17:31:50</td><td>200</td><td>11522.770166397095</td><td>8</td><td></td></tr><tr><td>19</td><td>2025-12-19 17:31:50</td><td>200</td><td>11687.269687652588</td><td>8</td><td></td></tr><tr><td>84</td><td>2025-12-19 17:31:50</td><td>200</td><td>11723.859310150146</td><td>8</td><td></td></tr><tr><td>85</td><td>2025-12-19 17:31:51</td><td>200</td><td>12288.209676742554</td><td>8</td><td></td></tr><tr><td>11</td><td>2025-12-19 17:31:52</td><td>200</td><td>13604.55298423767</td><td>8</td><td></td></tr><tr><td>43</td><td>2025-12-19 17:31:54</td><td>200</td><td>15098.32787513733</td><td>8</td><td></td></tr><tr><td>48</td><td>2025-12-19 17:31:55</td><td>200</td><td>16122.00403213501</td><td>8</td><td></td></tr><tr><td>5</td><td>2025-12-19 17:31:55</td><td>200</td><td>16604.77638244629</td><td>8</td><td></td></tr><tr><td>63</td><td>2025-12-19 17:31:55</td><td>200</td><td>16740.970611572266</td><td>8</td><td></td></tr><tr><td>87</td><td>2025-12-19 17:31:56</td><td>200</td><td>16841.984748840332</td><td>8</td><td></td></tr><tr><td>39</td><td>2025-12-19 17:31:56</td><td>200</td><td>17636.683702468872</td><td>8</td><td></td></tr><tr><td>71</td><td>2025-12-19 17:31:58</td><td>200</td><td>19641.02077484131</td><td>8</td><td></td></tr><tr><td>15</td><td>2025-12-19 17:32:00</td><td>200</td><td>20874.3417263031</td><td>8</td><td></td></tr><tr><td>88</td><td>2025-12-19 17:32:00</td><td>200</td><td>21562.708616256714</td><td>8</td><td></td></tr><tr><td>53</td><td>2025-12-19 17:32:01</td><td>200</td><td>21881.85214996338</td><td>8</td><td></td></tr><tr><td>8</td><td>2025-12-19 17:32:01</td><td>200</td><td>21970.46160697937</td><td>8</td><td></td></tr><tr><td>92</td><td>2025-12-19 17:32:01</td><td>200</td><td>21976.45878791809</td><td>8</td><td></td></tr><tr><td>90</td><td>2025-12-19 17:32:02</td><td>200</td><td>22839.781284332275</td><td>8</td><td></td></tr><tr><td>14</td><td>2025-12-19 17:32:04</td><td>200</td><td>25562.65163421631</td><td>8</td><td></td></tr><tr><td>6</td><td>2025-12-19 17:32:04</td><td>200</td><td>25733.7863445282</td><td>8</td><td></td></tr><tr><td>16</td><td>2025-12-19 17:32:05</td><td>200</td><td>26594.603061676025</td><td>8</td><td></td></tr><tr><td>42</td><td>2025-12-19 17:32:06</td><td>200</td><td>26934.176206588745</td><td>8</td><td></td></tr><tr><td>12</td><td>2025-12-19 17:32:06</td><td>200</td><td>27078.773975372314</td><td>8</td><td></td></tr><tr><td>95</td><td>2025-12-19 17:32:07</td><td>200</td><td>28102.483987808228</td><td>8</td><td></td></tr><tr><td>34</td><td>2025-12-19 17:32:07</td><td>200</td><td>28775.54178237915</td><td>8</td><td></td></tr><tr><td>76</td><td>2025-12-19 17:32:09</td><td>200</td><td>30250.354528427124</td><td>8</td><td></td></tr><tr><td>41</td><td>2025-12-19 17:32:10</td><td>200</td><td>31592.986345291138</td><td>8</td><td></td></tr><tr><td>46</td><td>2025-12-19 17:32:10</td><td>200</td><td>31739.563703536987</td><td>8</td><td></td></tr><tr><td>78</td><td>2025-12-19 17:32:11</td><td>200</td><td>31971.235513687134</td><td>8</td><td></td></tr><tr><td>1</td><td>2025-12-19 17:32:11</td><td>200</td><td>32221.757173538208</td><td>8</td><td></td></tr><tr><td>30</td><td>2025-12-19 17:32:12</td><td>200</td><td>33461.28559112549</td><td>8</td><td></td></tr><tr><td>89</td><td>2025-12-19 17:32:14</td><td>200</td><td>34931.60033226013</td><td>8</td><td></td></tr><tr><td>79</td><td>2025-12-19 17:32:14</td><td>200</td><td>35573.57048988342</td><td>8</td><td></td></tr><tr><td>49</td><td>2025-12-19 17:32:15</td><td>200</td><td>36576.865673065186</td><td>8</td><td></td></tr><tr><td>27</td><td>2025-12-19 17:32:16</td><td>200</td><td>37026.54409408569</td><td>8</td><td></td></tr><tr><td>75</td><td>2025-12-19 17:32:16</td><td>200</td><td>37259.377002716064</td><td>8</td><td></td></tr><tr><td>77</td><td>2025-12-19 17:32:17</td><td>200</td><td>37770.089626312256</td><td>8</td><td></td></tr><tr><td>7</td><td>2025-12-19 17:32:17</td><td>200</td><td>38723.75154495239</td><td>8</td><td></td></tr><tr><td>24</td><td>2025-12-19 17:32:18</td><td>200</td><td>39714.79916572571</td><td>8</td><td></td></tr><tr><td>45</td><td>2025-12-19 17:32:20</td><td>200</td><td>41640.74349403381</td><td>8</td><td></td></tr><tr><td>83</td><td>2025-12-19 17:32:21</td><td>200</td><td>42098.19722175598</td><td>8</td><td></td></tr><tr><td>69</td><td>2025-12-19 17:32:21</td><td>200</td><td>42372.363567352295</td><td>8</td><td></td></tr><tr><td>65</td><td>2025-12-19 17:32:21</td><td>200</td><td>42404.36124801636</td><td>8</td><td></td></tr><tr><td>66</td><td>2025-12-19 17:32:23</td><td>200</td><td>43801.53155326843</td><td>8</td><td></td></tr><tr><td>36</td><td>2025-12-19 17:32:23</td><td>200</td><td>43959.1498374939</td><td>8</td><td></td></tr><tr><td>61</td><td>2025-12-19 17:32:23</td><td>200</td><td>44357.62405395508</td><td>8</td><td></td></tr><tr><td>50</td><td>2025-12-19 17:32:25</td><td>200</td><td>46581.279039382935</td><td>8</td><td></td></tr><tr><td>86</td><td>2025-12-19 17:32:26</td><td>200</td><td>47098.071336746216</td><td>8</td><td></td></tr><tr><td>64</td><td>2025-12-19 17:32:26</td><td>200</td><td>47479.154109954834</td><td>8</td><td></td></tr><tr><td>10</td><td>2025-12-19 17:32:28</td><td>200</td><td>49046.358823776245</td><td>8</td><td></td></tr><tr><td>62</td><td>2025-12-19 17:32:28</td><td>200</td><td>49171.348571777344</td><td>8</td><td></td></tr><tr><td>17</td><td>2025-12-19 17:32:28</td><td>200</td><td>49231.353998184204</td><td>8</td><td></td></tr><tr><td>100</td><td>2025-12-19 17:32:29</td><td>200</td><td>49789.0567779541</td><td>8</td><td></td></tr><tr><td>93</td><td>2025-12-19 17:32:30</td><td>200</td><td>51503.05223464966</td><td>8</td><td></td></tr><tr><td>33</td><td>2025-12-19 17:32:31</td><td>200</td><td>52132.91811943054</td><td>8</td><td></td></tr><tr><td>29</td><td>2025-12-19 17:32:31</td><td>200</td><td>52545.90559005737</td><td>8</td><td></td></tr><tr><td>68</td><td>2025-12-19 17:32:32</td><td>200</td><td>53702.36945152283</td><td>8</td><td></td></tr><tr><td>58</td><td>2025-12-19 17:32:33</td><td>200</td><td>54482.28311538696</td><td>8</td><td></td></tr><tr><td>18</td><td>2025-12-19 17:32:35</td><td>200</td><td>55861.69981956482</td><td>8</td><td></td></tr><tr><td>44</td><td>2025-12-19 17:32:35</td><td>200</td><td>55958.28986167908</td><td>8</td><td></td></tr><tr><td>94</td><td>2025-12-19 17:32:35</td><td>200</td><td>56471.10819816589</td><td>8</td><td></td></tr><tr><td>51</td><td>2025-12-19 17:32:36</td><td>200</td><td>57140.769720077515</td><td>8</td><td></td></tr><tr><td>57</td><td>2025-12-19 17:32:36</td><td>200</td><td>57601.84645652771</td><td>8</td><td></td></tr><tr><td>91</td><td>2025-12-19 17:32:37</td><td>200</td><td>58333.40525627136</td><td>8</td><td></td></tr><tr><td>56</td><td>2025-12-19 17:32:38</td><td>200</td><td>59769.74272727966</td><td>8</td><td></td></tr><tr><td>73</td><td>2025-12-19 17:34:53</td><td>0</td><td>60012.06016540527</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>3</td><td>2025-12-19 17:34:53</td><td>0</td><td>60029.062271118164</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>54</td><td>2025-12-19 17:34:53</td><td>0</td><td>60038.05732727051</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>67</td><td>2025-12-19 17:34:53</td><td>0</td><td>60053.062200546265</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>13</td><td>2025-12-19 17:34:53</td><td>0</td><td>60050.058364868164</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>99</td><td>2025-12-19 17:34:53</td><td>0</td><td>60063.05909156799</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>82</td><td>2025-12-19 17:34:53</td><td>0</td><td>60063.05980682373</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>2</td><td>2025-12-19 17:34:53</td><td>0</td><td>60065.059185028076</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>81</td><td>2025-12-19 17:34:53</td><td>0</td><td>60067.060708999634</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>20</td><td>2025-12-19 17:34:53</td><td>0</td><td>60065.06037712097</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>22</td><td>2025-12-19 17:34:53</td><td>0</td><td>60066.06078147888</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>52</td><td>2025-12-19 17:34:53</td><td>0</td><td>60068.06182861328</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>97</td><td>2025-12-19 17:34:53</td><td>0</td><td>60069.05698776245</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>60</td><td>2025-12-19 17:34:53</td><td>0</td><td>60067.06118583679</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>9</td><td>2025-12-19 17:34:53</td><td>0</td><td>60012.06111907959</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>70</td><td>2025-12-19 17:34:53</td><td>0</td><td>60008.060932159424</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>23</td><td>2025-12-19 17:34:53</td><td>0</td><td>60012.06111907959</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>28</td><td>2025-12-19 17:34:53</td><td>0</td><td>60007.062673568726</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>25</td><td>2025-12-19 17:34:53</td><td>0</td><td>60031.68296813965</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>40</td><td>2025-12-19 17:34:53</td><td>0</td><td>60049.68023300171</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>59</td><td>2025-12-19 17:34:53</td><td>0</td><td>60065.68241119385</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>55</td><td>2025-12-19 17:34:53</td><td>0</td><td>60065.680742263794</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>26</td><td>2025-12-19 17:34:53</td><td>0</td><td>60068.69339942932</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>74</td><td>2025-12-19 17:34:53</td><td>0</td><td>60069.206953048706</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>38</td><td>2025-12-19 17:34:53</td><td>0</td><td>60067.683935165405</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>72</td><td>2025-12-19 17:34:53</td><td>0</td><td>60063.68398666382</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr><tr><td>32</td><td>2025-12-19 17:34:53</td><td>0</td><td>60062.68334388733</td><td>None</td><td>status_404:<html>
|
| | | <head><title>404 Not Found</title></head>
|
| | | <body>
|
| | | <center><h1>404 Not Found</h1></center>
|
| | | <hr><center>nginx/1.26.2</center>
|
| | | </body>
|
| | | </html>
|
| | | </td></tr>
|
| | | </table>
|
| | | |
| | | <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p>
|
| | | </body>
|
| | | </html>
|
| | | |
| New file |
| | |
| | | """ |
| | | 集成压测脚本(带压测报告生成并通过钉钉发送摘要) |
| | | |
| | | 说明: |
| | | - 该脚本基于之前的稳定 worker/队列 实现,运行后会记录每条请求的时间、状态码和延迟。 |
| | | - 运行结束后会调用Util目录下的压测报告生成器(文件名: stress_test_report_generator.py),输出 HTML/JSON/CSV/(可选)DOCX 等文件。 |
| | | - 生成后会把关键统计摘要通过 DingTalk 机器人发送(调用 DingTalkHelper.send_message)。 |
| | | - 安装依赖:aiohttp, tqdm, numpy/pandas/matplotlib/python-docx(可选) |
| | | - 确保 DingTalkHelper 类在你的 `Util.dingtalk_helper` 中可用,且 ACCESS_TOKEN/SECRET 正确。 |
| | | """ |
| | | import sys |
| | | import os |
| | | # 将上一级目录加入模块搜索路径 |
| | | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| | | import asyncio |
| | | import aiohttp |
| | | import time |
| | | import traceback |
| | | import datetime |
| | | from tqdm import tqdm |
| | | from Util.random_util import RandomUtil |
| | | from Util.dingtalk_helper import DingTalkHelper |
| | | |
| | | |
| | | # --- 配置 --- |
| | | ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d' |
| | | SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19' |
| | | |
| | | apiname = "创建动物房笼架" |
| | | url = "http://192.168.6.190:5561/api/base/room/shelf/save" |
| | | headers = { |
| | | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjYyMTk5MzIsInVzZXJuYW1lIjoiZ2x5In0.fN-yJ0SVrEd_GyIvPmaBL2nx1M3a1jloSOsXyu3E5_w", |
| | | "Content-Type": "application/json" |
| | | } |
| | | |
| | | NUM_WORKERS = 100 |
| | | TOTAL_REQUESTS = 40000 |
| | | MAX_RETRIES = 3 |
| | | REQUEST_TIMEOUT = 60 |
| | | OUTPUT_DIR = './load_test_report' |
| | | |
| | | # --- 初始化 --- |
| | | dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET) |
| | | |
| | | LARGE_CONTENT = "压测中" * 500 |
| | | FILES_PATH = "/userfiles/1588133301094375425/程序附件/notify/notify/2025/10/15/173933/cs.jpg" |
| | | |
| | | |
| | | def create_animal_data(idx: int): |
| | | random_code = RandomUtil.generate_random_number_string(0, 999999999) |
| | | random_data = RandomUtil.generate_random_date("2023-01-01", "2025-12-19") |
| | | return { |
| | | "id": "", |
| | | "room": { |
| | | "id": "2001936175429365762" |
| | | }, |
| | | "facility": { |
| | | "id": "" |
| | | }, |
| | | "name": f"hyb压测笼架{random_code}", |
| | | "code": f"hyb压测笼架{random_code}", |
| | | "status": "1", |
| | | "internalPrice": "", |
| | | "externalPrice": "", |
| | | "negotiatedPrice": "", |
| | | "row": 10, |
| | | "col": 10, |
| | | "leftRight": "5", |
| | | "topBottom": "5", |
| | | "remarks": "", |
| | | "priceConfig": { |
| | | "id": "1860888070482100226" |
| | | }, |
| | | "variety": {}, |
| | | "shelfCleanDate": random_data, |
| | | "otherCleanDate": random_data |
| | | } |
| | | |
| | | |
| | | async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES): |
| | | attempt = 0 |
| | | last_err = None |
| | | while attempt < max_retries: |
| | | data = create_animal_data(index) |
| | | start = time.time() |
| | | try: |
| | | async with session.post(url, json=data, headers=headers) as resp: |
| | | text = await resp.text() |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | status = resp.status |
| | | if status == 200: |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': status, |
| | | 'latency_ms': latency_ms, |
| | | 'response_size': len(text) if text is not None else None, |
| | | 'error': None |
| | | } |
| | | else: |
| | | last_err = f'status_{status}:{text}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | except Exception as e: |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | last_err = f'{type(e).__name__}:{str(e)}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | # 最终失败 |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': 0, |
| | | 'latency_ms': latency_ms if 'latency_ms' in locals() else 0, |
| | | 'response_size': None, |
| | | 'error': last_err |
| | | } |
| | | |
| | | |
| | | async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock): |
| | | while True: |
| | | idx = await queue.get() |
| | | if idx is None: |
| | | queue.task_done() |
| | | break |
| | | try: |
| | | res = await perform_request(session, idx) |
| | | # 记录到报告生成器 |
| | | gen.record_result( |
| | | index=res['index'], |
| | | timestamp=res['timestamp'], |
| | | status_code=int(res['status_code']), |
| | | latency_ms=float(res['latency_ms']), |
| | | response_size=res.get('response_size'), |
| | | error=res.get('error') |
| | | ) |
| | | async with lock: |
| | | if res['status_code'] and 200 <= res['status_code'] < 300: |
| | | success_counter['count'] += 1 |
| | | else: |
| | | failed_list.append((res['index'], res.get('error'))) |
| | | pbar.update(1) |
| | | except Exception as e: |
| | | async with lock: |
| | | failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}')) |
| | | pbar.update(1) |
| | | finally: |
| | | queue.task_done() |
| | | |
| | | |
| | | async def batch_create_animals(total: int, num_workers: int): |
| | | # 动态加载报告生成器模块(支持中文文件名) |
| | | gen = None |
| | | try: |
| | | import importlib.util |
| | | script_dir = os.path.dirname(os.path.abspath(__file__)) |
| | | report_path = os.path.join(script_dir, 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py') |
| | | if os.path.exists(report_path): |
| | | spec = importlib.util.spec_from_file_location('report_module', report_path) |
| | | report_module = importlib.util.module_from_spec(spec) |
| | | spec.loader.exec_module(report_module) |
| | | LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator') |
| | | else: |
| | | # 备用:尝试直接导入模块名(若你的文件名已改为 ascii) |
| | | from report_generator import LoadTestReportGenerator # type: ignore |
| | | gen = LoadTestReportGenerator(test_name='压测任务', report_title='压测详细报告') |
| | | except Exception as e: |
| | | print('无法加载压测报告生成器,请确认stress_test_report_generator.py 文件位置正确。\n', e) |
| | | raise |
| | | |
| | | timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT) |
| | | connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False) |
| | | async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session: |
| | | queue = asyncio.Queue() |
| | | for i in range(1, total + 1): |
| | | await queue.put(i) |
| | | for _ in range(num_workers): |
| | | await queue.put(None) |
| | | |
| | | success_counter = {'count': 0} |
| | | failed_list = [] |
| | | lock = asyncio.Lock() |
| | | |
| | | with tqdm(total=total, desc='创建进度') as pbar: |
| | | workers = [ |
| | | asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock)) |
| | | for i in range(num_workers) |
| | | ] |
| | | await asyncio.gather(*workers) |
| | | |
| | | # 任务完成,生成报告 |
| | | os.makedirs(OUTPUT_DIR, exist_ok=True) |
| | | outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx']) |
| | | |
| | | stats = gen.compute_stats() |
| | | |
| | | # 构造钉钉摘要消息(中文) |
| | | now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
| | | msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}'] |
| | | msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}") |
| | | msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}") |
| | | lat = stats.get('latency_ms', {}) |
| | | msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}") |
| | | |
| | | # 列出生成的报告文件 |
| | | file_list = [] |
| | | for k, v in outputs.items(): |
| | | if k == 'charts': |
| | | for cname, cpath in v.items(): |
| | | file_list.append(os.path.abspath(cpath)) |
| | | else: |
| | | file_list.append(os.path.abspath(v)) |
| | | msg.append('生成文件:') |
| | | for p in file_list: |
| | | msg.append(p) |
| | | |
| | | final_msg = '\n'.join(msg) |
| | | |
| | | # 发送钉钉消息 |
| | | try: |
| | | dingtalk_helper.send_message(final_msg) |
| | | except Exception as e: |
| | | print('发送钉钉消息失败:', e) |
| | | |
| | | print('\n[SUMMARY] 已生成报告并发送钉钉摘要。') |
| | | print('成功数:', success_counter['count'], ' 失败数:', len(failed_list)) |
| | | if failed_list: |
| | | print('失败示例(最多显示50条):') |
| | | for idx, err in failed_list[:50]: |
| | | print(f' #{idx} => {err}') |
| | | |
| | | |
| | | if __name__ == '__main__': |
| | | # 运行前建议先用小规模测试 |
| | | TOTAL_REQUESTS = TOTAL_REQUESTS |
| | | NUM_WORKERS = NUM_WORKERS |
| | | asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS)) |
| New file |
| | |
| | | |
| | | <!doctype html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="utf-8"> |
| | | <title>压测详细报告</title> |
| | | <style> |
| | | body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px} |
| | | h2{color:#2c3e50} |
| | | table{border-collapse:collapse; width:100%} |
| | | th,td{padding:6px; text-align:left} |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>压测详细报告</h1> |
| | | |
| | | <h2>摘要</h2> |
| | | <ul> |
| | | <li>报告名称:压测详细报告</li> |
| | | <li>生成时间:2025-10-16 23:35:15</li> |
| | | <li>总请求数:100000</li> |
| | | <li>成功数:100000,失败数:0,成功率:100.00%</li> |
| | | <li>总耗时(秒):20485.64</li> |
| | | <li>平均吞吐(req/s):4.88</li> |
| | | </ul> |
| | | |
| | | |
| | | <h2>响应时间统计 (ms)</h2> |
| | | <ul> |
| | | <li>最小:6943.04</li> |
| | | <li>最大:59075.08</li> |
| | | <li>平均:20493.84</li> |
| | | <li>中位数(P50):20024.88</li> |
| | | <li>P90:26928.12,P95:28865.69,P99:32812.51</li> |
| | | </ul> |
| | | |
| | | <h2>状态码分布</h2><ul><li>200: 100000</li></ul> |
| | | <h2>错误汇总</h2><p>无错误记录</p> |
| | | <h2>图表</h2><div><h3>latency_hist</h3><img src="个体信息压测任务_20251016_233508_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="个体信息压测任务_20251016_233508_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div> |
| | | |
| | | <h2>请求明细(仅显示前100条)</h2> |
| | | <table border="1" cellpadding="4" cellspacing="0"> |
| | | <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr> |
| | | <tr><td>1</td><td>2025-10-16 17:53:42</td><td>200</td><td>11861.668586730957</td><td>4</td><td></td></tr><tr><td>36</td><td>2025-10-16 17:53:42</td><td>200</td><td>11966.110467910767</td><td>4</td><td></td></tr><tr><td>46</td><td>2025-10-16 17:53:42</td><td>200</td><td>12062.110424041748</td><td>4</td><td></td></tr><tr><td>49</td><td>2025-10-16 17:53:42</td><td>200</td><td>12132.709980010986</td><td>4</td><td></td></tr><tr><td>24</td><td>2025-10-16 17:53:42</td><td>200</td><td>12169.715166091919</td><td>4</td><td></td></tr><tr><td>15</td><td>2025-10-16 17:53:42</td><td>200</td><td>12181.530475616455</td><td>4</td><td></td></tr><tr><td>4</td><td>2025-10-16 17:53:42</td><td>200</td><td>12190.528869628906</td><td>4</td><td></td></tr><tr><td>9</td><td>2025-10-16 17:53:42</td><td>200</td><td>12194.527864456177</td><td>4</td><td></td></tr><tr><td>38</td><td>2025-10-16 17:53:42</td><td>200</td><td>12184.709787368774</td><td>4</td><td></td></tr><tr><td>11</td><td>2025-10-16 17:53:42</td><td>200</td><td>12212.530851364136</td><td>4</td><td></td></tr><tr><td>25</td><td>2025-10-16 17:53:42</td><td>200</td><td>12218.717575073242</td><td>4</td><td></td></tr><tr><td>22</td><td>2025-10-16 17:53:42</td><td>200</td><td>12237.71619796753</td><td>4</td><td></td></tr><tr><td>29</td><td>2025-10-16 17:53:42</td><td>200</td><td>12231.717109680176</td><td>4</td><td></td></tr><tr><td>8</td><td>2025-10-16 17:53:42</td><td>200</td><td>12252.525568008423</td><td>4</td><td></td></tr><tr><td>53</td><td>2025-10-16 17:53:42</td><td>200</td><td>12225.710391998291</td><td>4</td><td></td></tr><tr><td>2</td><td>2025-10-16 17:53:42</td><td>200</td><td>12263.529062271118</td><td>4</td><td></td></tr><tr><td>13</td><td>2025-10-16 17:53:42</td><td>200</td><td>12257.530450820923</td><td>4</td><td></td></tr><tr><td>6</td><td>2025-10-16 17:53:42</td><td>200</td><td>12269.530534744263</td><td>4</td><td></td></tr><tr><td>5</td><td>2025-10-16 17:53:42</td><td>200</td><td>12271.530151367188</td><td>4</td><td></td></tr><tr><td>44</td><td>2025-10-16 17:53:42</td><td>200</td><td>12255.710124969482</td><td>4</td><td></td></tr><tr><td>100</td><td>2025-10-16 17:53:42</td><td>200</td><td>12219.7105884552</td><td>4</td><td></td></tr><tr><td>42</td><td>2025-10-16 17:53:42</td><td>200</td><td>12263.710498809814</td><td>4</td><td></td></tr><tr><td>30</td><td>2025-10-16 17:53:42</td><td>200</td><td>12285.7186794281</td><td>4</td><td></td></tr><tr><td>45</td><td>2025-10-16 17:53:42</td><td>200</td><td>12279.711246490479</td><td>4</td><td></td></tr><tr><td>39</td><td>2025-10-16 17:53:42</td><td>200</td><td>12284.71040725708</td><td>4</td><td></td></tr><tr><td>33</td><td>2025-10-16 17:53:42</td><td>200</td><td>12290.711879730225</td><td>4</td><td></td></tr><tr><td>26</td><td>2025-10-16 17:53:42</td><td>200</td><td>12311.718225479126</td><td>4</td><td></td></tr><tr><td>70</td><td>2025-10-16 17:53:42</td><td>200</td><td>12281.710863113403</td><td>4</td><td></td></tr><tr><td>72</td><td>2025-10-16 17:53:42</td><td>200</td><td>12282.710552215576</td><td>4</td><td></td></tr><tr><td>32</td><td>2025-10-16 17:53:42</td><td>200</td><td>12311.718225479126</td><td>4</td><td></td></tr><tr><td>81</td><td>2025-10-16 17:53:42</td><td>200</td><td>12280.710697174072</td><td>4</td><td></td></tr><tr><td>43</td><td>2025-10-16 17:53:42</td><td>200</td><td>12316.7085647583</td><td>4</td><td></td></tr><tr><td>31</td><td>2025-10-16 17:53:42</td><td>200</td><td>12328.720092773438</td><td>4</td><td></td></tr><tr><td>61</td><td>2025-10-16 17:53:42</td><td>200</td><td>12313.71021270752</td><td>4</td><td></td></tr><tr><td>12</td><td>2025-10-16 17:53:42</td><td>200</td><td>12350.529909133911</td><td>4</td><td></td></tr><tr><td>34</td><td>2025-10-16 17:53:42</td><td>200</td><td>12343.712329864502</td><td>4</td><td></td></tr><tr><td>16</td><td>2025-10-16 17:53:42</td><td>200</td><td>12361.530542373657</td><td>4</td><td></td></tr><tr><td>85</td><td>2025-10-16 17:53:42</td><td>200</td><td>12318.710327148438</td><td>4</td><td></td></tr><tr><td>41</td><td>2025-10-16 17:53:42</td><td>200</td><td>12351.709842681885</td><td>4</td><td></td></tr><tr><td>59</td><td>2025-10-16 17:53:42</td><td>200</td><td>12344.709634780884</td><td>4</td><td></td></tr><tr><td>35</td><td>2025-10-16 17:53:42</td><td>200</td><td>12367.71035194397</td><td>4</td><td></td></tr><tr><td>98</td><td>2025-10-16 17:53:42</td><td>200</td><td>12328.714370727539</td><td>4</td><td></td></tr><tr><td>96</td><td>2025-10-16 17:53:42</td><td>200</td><td>12338.710069656372</td><td>4</td><td></td></tr><tr><td>47</td><td>2025-10-16 17:53:42</td><td>200</td><td>12372.710943222046</td><td>4</td><td></td></tr><tr><td>57</td><td>2025-10-16 17:53:42</td><td>200</td><td>12365.710496902466</td><td>4</td><td></td></tr><tr><td>86</td><td>2025-10-16 17:53:42</td><td>200</td><td>12354.711294174194</td><td>4</td><td></td></tr><tr><td>78</td><td>2025-10-16 17:53:42</td><td>200</td><td>12365.710258483887</td><td>4</td><td></td></tr><tr><td>55</td><td>2025-10-16 17:53:42</td><td>200</td><td>12382.710695266724</td><td>4</td><td></td></tr><tr><td>95</td><td>2025-10-16 17:53:42</td><td>200</td><td>12361.716270446777</td><td>4</td><td></td></tr><tr><td>19</td><td>2025-10-16 17:53:42</td><td>200</td><td>12427.526950836182</td><td>4</td><td></td></tr><tr><td>40</td><td>2025-10-16 17:53:42</td><td>200</td><td>12411.710739135742</td><td>4</td><td></td></tr><tr><td>10</td><td>2025-10-16 17:53:42</td><td>200</td><td>12435.532331466675</td><td>4</td><td></td></tr><tr><td>97</td><td>2025-10-16 17:53:42</td><td>200</td><td>12391.710758209229</td><td>4</td><td></td></tr><tr><td>94</td><td>2025-10-16 17:53:42</td><td>200</td><td>12395.710706710815</td><td>4</td><td></td></tr><tr><td>93</td><td>2025-10-16 17:53:43</td><td>200</td><td>12413.711786270142</td><td>4</td><td></td></tr><tr><td>56</td><td>2025-10-16 17:53:43</td><td>200</td><td>12442.712783813477</td><td>4</td><td></td></tr><tr><td>60</td><td>2025-10-16 17:53:43</td><td>200</td><td>12457.717657089233</td><td>4</td><td></td></tr><tr><td>51</td><td>2025-10-16 17:53:43</td><td>200</td><td>12465.712547302246</td><td>4</td><td></td></tr><tr><td>54</td><td>2025-10-16 17:53:43</td><td>200</td><td>12464.71357345581</td><td>4</td><td></td></tr><tr><td>23</td><td>2025-10-16 17:53:43</td><td>200</td><td>12505.717515945435</td><td>4</td><td></td></tr><tr><td>71</td><td>2025-10-16 17:53:43</td><td>200</td><td>12472.711324691772</td><td>4</td><td></td></tr><tr><td>82</td><td>2025-10-16 17:53:43</td><td>200</td><td>12466.711282730103</td><td>4</td><td></td></tr><tr><td>80</td><td>2025-10-16 17:53:43</td><td>200</td><td>12479.711771011353</td><td>4</td><td></td></tr><tr><td>87</td><td>2025-10-16 17:53:43</td><td>200</td><td>12482.711553573608</td><td>4</td><td></td></tr><tr><td>99</td><td>2025-10-16 17:53:43</td><td>200</td><td>12477.71143913269</td><td>4</td><td></td></tr><tr><td>69</td><td>2025-10-16 17:53:43</td><td>200</td><td>12505.709886550903</td><td>4</td><td></td></tr><tr><td>58</td><td>2025-10-16 17:53:43</td><td>200</td><td>12523.711204528809</td><td>4</td><td></td></tr><tr><td>84</td><td>2025-10-16 17:53:43</td><td>200</td><td>12509.711503982544</td><td>4</td><td></td></tr><tr><td>91</td><td>2025-10-16 17:53:43</td><td>200</td><td>12524.71137046814</td><td>4</td><td></td></tr><tr><td>67</td><td>2025-10-16 17:53:43</td><td>200</td><td>12543.71166229248</td><td>4</td><td></td></tr><tr><td>73</td><td>2025-10-16 17:53:43</td><td>200</td><td>12558.297157287598</td><td>4</td><td></td></tr><tr><td>3</td><td>2025-10-16 17:53:43</td><td>200</td><td>12613.115549087524</td><td>4</td><td></td></tr><tr><td>7</td><td>2025-10-16 17:53:43</td><td>200</td><td>12627.113103866577</td><td>4</td><td></td></tr><tr><td>65</td><td>2025-10-16 17:53:43</td><td>200</td><td>12596.299171447754</td><td>4</td><td></td></tr><tr><td>76</td><td>2025-10-16 17:53:43</td><td>200</td><td>12598.296880722046</td><td>4</td><td></td></tr><tr><td>48</td><td>2025-10-16 17:53:43</td><td>200</td><td>12659.295320510864</td><td>4</td><td></td></tr><tr><td>63</td><td>2025-10-16 17:53:43</td><td>200</td><td>12650.29788017273</td><td>4</td><td></td></tr><tr><td>79</td><td>2025-10-16 17:53:43</td><td>200</td><td>12640.297889709473</td><td>4</td><td></td></tr><tr><td>28</td><td>2025-10-16 17:53:43</td><td>200</td><td>12696.300983428955</td><td>4</td><td></td></tr><tr><td>62</td><td>2025-10-16 17:53:43</td><td>200</td><td>12693.297624588013</td><td>4</td><td></td></tr><tr><td>88</td><td>2025-10-16 17:53:43</td><td>200</td><td>12678.295850753784</td><td>4</td><td></td></tr><tr><td>27</td><td>2025-10-16 17:53:43</td><td>200</td><td>12736.303329467773</td><td>4</td><td></td></tr><tr><td>21</td><td>2025-10-16 17:53:43</td><td>200</td><td>12751.317262649536</td><td>4</td><td></td></tr><tr><td>89</td><td>2025-10-16 17:53:43</td><td>200</td><td>12715.296506881714</td><td>4</td><td></td></tr><tr><td>66</td><td>2025-10-16 17:53:43</td><td>200</td><td>12741.296768188477</td><td>4</td><td></td></tr><tr><td>52</td><td>2025-10-16 17:53:43</td><td>200</td><td>12762.2971534729</td><td>4</td><td></td></tr><tr><td>14</td><td>2025-10-16 17:53:43</td><td>200</td><td>12801.117181777954</td><td>4</td><td></td></tr><tr><td>75</td><td>2025-10-16 17:53:43</td><td>200</td><td>12768.296718597412</td><td>4</td><td></td></tr><tr><td>92</td><td>2025-10-16 17:53:43</td><td>200</td><td>12776.912212371826</td><td>4</td><td></td></tr><tr><td>77</td><td>2025-10-16 17:53:43</td><td>200</td><td>12795.911073684692</td><td>4</td><td></td></tr><tr><td>17</td><td>2025-10-16 17:53:43</td><td>200</td><td>12852.729082107544</td><td>4</td><td></td></tr><tr><td>90</td><td>2025-10-16 17:53:43</td><td>200</td><td>12802.912473678589</td><td>4</td><td></td></tr><tr><td>64</td><td>2025-10-16 17:53:43</td><td>200</td><td>13238.07168006897</td><td>4</td><td></td></tr><tr><td>68</td><td>2025-10-16 17:53:46</td><td>200</td><td>15427.53529548645</td><td>4</td><td></td></tr><tr><td>50</td><td>2025-10-16 17:53:46</td><td>200</td><td>16027.122259140015</td><td>4</td><td></td></tr><tr><td>37</td><td>2025-10-16 17:53:47</td><td>200</td><td>17389.598608016968</td><td>4</td><td></td></tr><tr><td>20</td><td>2025-10-16 17:53:48</td><td>200</td><td>17629.64653968811</td><td>4</td><td></td></tr><tr><td>18</td><td>2025-10-16 17:53:48</td><td>200</td><td>17783.334016799927</td><td>4</td><td></td></tr><tr><td>74</td><td>2025-10-16 17:53:50</td><td>200</td><td>20080.499410629272</td><td>4</td><td></td></tr><tr><td>83</td><td>2025-10-16 17:53:50</td><td>200</td><td>20292.65594482422</td><td>4</td><td></td></tr> |
| | | </table> |
| | | |
| | | <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p> |
| | | </body> |
| | | </html> |
| | | |
| New file |
| | |
| | | """ |
| | | 集成压测脚本(带压测报告生成并通过钉钉发送摘要) |
| | | |
| | | 说明: |
| | | - 该脚本基于之前的稳定 worker/队列 实现,运行后会记录每条请求的时间、状态码和延迟。 |
| | | - 运行结束后会调用Util目录下的压测报告生成器(文件名: stress_test_report_generator.py),输出 HTML/JSON/CSV/(可选)DOCX 等文件。 |
| | | - 生成后会把关键统计摘要通过 DingTalk 机器人发送(调用 DingTalkHelper.send_message)。 |
| | | - 安装依赖:aiohttp, tqdm, numpy/pandas/matplotlib/python-docx(可选) |
| | | - 确保 DingTalkHelper 类在你的 `Util.dingtalk_helper` 中可用,且 ACCESS_TOKEN/SECRET 正确。 |
| | | """ |
| | | import sys |
| | | import os |
| | | # 将上一级目录加入模块搜索路径 |
| | | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| | | import asyncio |
| | | import aiohttp |
| | | import time |
| | | import traceback |
| | | import datetime |
| | | from tqdm import tqdm |
| | | from Util.random_util import RandomUtil |
| | | from Util.dingtalk_helper import DingTalkHelper |
| | | |
| | | |
| | | # --- 配置 --- |
| | | ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d' |
| | | SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19' |
| | | |
| | | apiname = "个体信息" |
| | | url = "http://192.168.6.168:5534/api/individual/individualrecord/individualRecord/save" |
| | | headers = { |
| | | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjA2Nzk1MDksInVzZXJuYW1lIjoiZ2x5In0.aApZ-cXC_pIw6gdPZHg3TlsgZIaCPRrmlMDM3-zEhtg", |
| | | "Content-Type": "application/json" |
| | | } |
| | | |
| | | NUM_WORKERS = 10 |
| | | TOTAL_REQUESTS = 100 |
| | | MAX_RETRIES = 3 |
| | | REQUEST_TIMEOUT = 60 |
| | | OUTPUT_DIR = './load_test_report' |
| | | |
| | | # --- 初始化 --- |
| | | dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET) |
| | | |
| | | LARGE_CONTENT = "备注压测中" * 10 |
| | | FILES_PATH = "/userfiles/1463828311460319233/程序附件//baoyi/individual/individualrecord/2025/10/cs.jpg" |
| | | |
| | | |
| | | def create_animal_data(idx: int): |
| | | random_code = RandomUtil.generate_random_number_string(0, 10000) |
| | | random_code_grade = RandomUtil.generate_random_number_string(1, 2) |
| | | random_code_sex = RandomUtil.generate_random_number_string(1, 2) |
| | | random_date = RandomUtil.generate_random_date("2023-01-01", "2025-10-16") |
| | | return { |
| | | "individualExperimentRecordDTOList": [ |
| | | { |
| | | "fileUrl": "", |
| | | "id": "", |
| | | "individualId": "", |
| | | "experimentDate": random_date, |
| | | "name": "3", |
| | | "otherName": "", |
| | | "remarks": "", |
| | | "ultrasoundScanDate": "", |
| | | "pregnancyStatus": "", |
| | | "otherSituation": "" |
| | | }, |
| | | { |
| | | "fileUrl": "", |
| | | "id": "", |
| | | "individualId": "", |
| | | "experimentDate": random_date, |
| | | "name": "5", |
| | | "otherName": "", |
| | | "remarks": "", |
| | | "ultrasoundScanDate": "", |
| | | "pregnancyStatus": "", |
| | | "otherSituation": "" |
| | | } |
| | | ], |
| | | "id": "", |
| | | "rfid": "random_code", |
| | | "code": f"hyb压测模拟万人新建动物信息{random_code}", |
| | | "variety": "食蟹猴", |
| | | "birthDate": f"{random_date} 00:00:00", |
| | | "deathDate": "", |
| | | "grade": random_code_grade, |
| | | "gender": random_code_sex, |
| | | "father": {"id": ""}, |
| | | "mother": {"id": ""}, |
| | | "birthPlace": "", |
| | | "room": {"id": "", "name": ""}, |
| | | "cage": {"id": "", "code": ""}, |
| | | "status": "5,2", |
| | | "weight": "", |
| | | "weightUnit": "", |
| | | "weightUnitValue": "", |
| | | "researchGroup": {"id": "1491301397488844801"}, |
| | | "isConfirmBirth": "1", |
| | | "birthYear": "2025", |
| | | "weightDate": "", |
| | | "remarks": LARGE_CONTENT, |
| | | "relevanceId": "", |
| | | "version": "", |
| | | "transferTime": "", |
| | | "transferRemarks": "", |
| | | "transferList": [], |
| | | "otherVariety": "", |
| | | "varietyType": "", |
| | | "animalSource": "1", |
| | | "enterTime": f"{random_date} 00:00:00", |
| | | "animalModel": "", |
| | | "project": {"id": "1825457771270230018"}, |
| | | "projectManager": {"id": "1810602921408172033"}, |
| | | "reminderTime": random_date, |
| | | "reminderUser": {"id": "1463828311460319233"}, |
| | | "fileUrl": FILES_PATH, |
| | | "groupChagneDate": random_date, |
| | | "infantId": "", |
| | | "statusStr": "5,2" |
| | | } |
| | | |
| | | |
| | | async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES): |
| | | attempt = 0 |
| | | last_err = None |
| | | while attempt < max_retries: |
| | | data = create_animal_data(index) |
| | | start = time.time() |
| | | try: |
| | | async with session.post(url, json=data, headers=headers) as resp: |
| | | text = await resp.text() |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | status = resp.status |
| | | if status == 200: |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': status, |
| | | 'latency_ms': latency_ms, |
| | | 'response_size': len(text) if text is not None else None, |
| | | 'error': None |
| | | } |
| | | else: |
| | | last_err = f'status_{status}:{text}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | except Exception as e: |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | last_err = f'{type(e).__name__}:{str(e)}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | # 最终失败 |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': 0, |
| | | 'latency_ms': latency_ms if 'latency_ms' in locals() else 0, |
| | | 'response_size': None, |
| | | 'error': last_err |
| | | } |
| | | |
| | | |
| | | async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock): |
| | | while True: |
| | | idx = await queue.get() |
| | | if idx is None: |
| | | queue.task_done() |
| | | break |
| | | try: |
| | | res = await perform_request(session, idx) |
| | | # 记录到报告生成器 |
| | | gen.record_result( |
| | | index=res['index'], |
| | | timestamp=res['timestamp'], |
| | | status_code=int(res['status_code']), |
| | | latency_ms=float(res['latency_ms']), |
| | | response_size=res.get('response_size'), |
| | | error=res.get('error') |
| | | ) |
| | | async with lock: |
| | | if res['status_code'] and 200 <= res['status_code'] < 300: |
| | | success_counter['count'] += 1 |
| | | else: |
| | | failed_list.append((res['index'], res.get('error'))) |
| | | pbar.update(1) |
| | | except Exception as e: |
| | | async with lock: |
| | | failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}')) |
| | | pbar.update(1) |
| | | finally: |
| | | queue.task_done() |
| | | |
| | | |
| | | async def batch_create_animals(total: int, num_workers: int): |
| | | # 动态加载报告生成器模块(支持中文文件名) |
| | | gen = None |
| | | try: |
| | | import importlib.util |
| | | script_dir = os.path.dirname(os.path.abspath(__file__)) |
| | | report_path = os.path.join(script_dir, 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py') |
| | | if os.path.exists(report_path): |
| | | spec = importlib.util.spec_from_file_location('report_module', report_path) |
| | | report_module = importlib.util.module_from_spec(spec) |
| | | spec.loader.exec_module(report_module) |
| | | LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator') |
| | | else: |
| | | # 备用:尝试直接导入模块名(若你的文件名已改为 ascii) |
| | | from report_generator import LoadTestReportGenerator # type: ignore |
| | | gen = LoadTestReportGenerator(test_name=f'{apiname}压测任务', report_title='压测详细报告') |
| | | except Exception as e: |
| | | print('无法加载压测报告生成器,请确认stress_test_report_generator.py 文件位置正确。\n', e) |
| | | raise |
| | | |
| | | timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT) |
| | | connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False) |
| | | async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session: |
| | | queue = asyncio.Queue() |
| | | for i in range(1, total + 1): |
| | | await queue.put(i) |
| | | for _ in range(num_workers): |
| | | await queue.put(None) |
| | | |
| | | success_counter = {'count': 0} |
| | | failed_list = [] |
| | | lock = asyncio.Lock() |
| | | |
| | | with tqdm(total=total, desc='创建进度') as pbar: |
| | | workers = [ |
| | | asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock)) |
| | | for i in range(num_workers) |
| | | ] |
| | | await asyncio.gather(*workers) |
| | | |
| | | # 任务完成,生成报告 |
| | | os.makedirs(OUTPUT_DIR, exist_ok=True) |
| | | outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx']) |
| | | |
| | | stats = gen.compute_stats() |
| | | |
| | | # 构造钉钉摘要消息(中文) |
| | | now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
| | | msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}'] |
| | | msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}") |
| | | msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}") |
| | | lat = stats.get('latency_ms', {}) |
| | | msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}") |
| | | |
| | | # 列出生成的报告文件 |
| | | file_list = [] |
| | | for k, v in outputs.items(): |
| | | if k == 'charts': |
| | | for cname, cpath in v.items(): |
| | | file_list.append(os.path.abspath(cpath)) |
| | | else: |
| | | file_list.append(os.path.abspath(v)) |
| | | msg.append('生成文件:') |
| | | for p in file_list: |
| | | msg.append(p) |
| | | |
| | | final_msg = '\n'.join(msg) |
| | | |
| | | # 发送钉钉消息 |
| | | try: |
| | | dingtalk_helper.send_message(final_msg) |
| | | except Exception as e: |
| | | print('发送钉钉消息失败:', e) |
| | | |
| | | print('\n[SUMMARY] 已生成报告并发送钉钉摘要。') |
| | | print('成功数:', success_counter['count'], ' 失败数:', len(failed_list)) |
| | | if failed_list: |
| | | print('失败示例(最多显示50条):') |
| | | for idx, err in failed_list[:50]: |
| | | print(f' #{idx} => {err}') |
| | | |
| | | |
| | | if __name__ == '__main__': |
| | | # 运行前建议先用小规模测试 |
| | | TOTAL_REQUESTS = TOTAL_REQUESTS |
| | | NUM_WORKERS = NUM_WORKERS |
| | | asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS)) |
| New file |
| | |
| | | """ |
| | | 集成压测脚本(带压测报告生成并通过钉钉发送摘要) |
| | | |
| | | 说明: |
| | | - 该脚本基于之前的稳定 worker/队列 实现,运行后会记录每条请求的时间、状态码和延迟。 |
| | | - 运行结束后会调用Util目录下的压测报告生成器(文件名: stress_test_report_generator.py),输出 HTML/JSON/CSV/(可选)DOCX 等文件。 |
| | | - 生成后会把关键统计摘要通过 DingTalk 机器人发送(调用 DingTalkHelper.send_message)。 |
| | | - 安装依赖:aiohttp, tqdm, numpy/pandas/matplotlib/python-docx(可选) |
| | | - 确保 DingTalkHelper 类在你的 `Util.dingtalk_helper` 中可用,且 ACCESS_TOKEN/SECRET 正确。 |
| | | """ |
| | | import sys |
| | | import os |
| | | # 将上一级目录加入模块搜索路径 |
| | | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| | | import asyncio |
| | | import aiohttp |
| | | import time |
| | | import traceback |
| | | import datetime |
| | | from tqdm import tqdm |
| | | from Util.random_util import RandomUtil |
| | | from Util.dingtalk_helper import DingTalkHelper |
| | | |
| | | |
| | | # --- 配置 --- |
| | | ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d' |
| | | SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19' |
| | | |
| | | apiname = "病毒检测" |
| | | url = "http://192.168.6.168:5534/api/detection/virusdetection/virusDetection/save" |
| | | headers = { |
| | | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjA2Nzk1MDksInVzZXJuYW1lIjoiZ2x5In0.aApZ-cXC_pIw6gdPZHg3TlsgZIaCPRrmlMDM3-zEhtg", |
| | | "Content-Type": "application/json" |
| | | } |
| | | |
| | | NUM_WORKERS = 100 |
| | | TOTAL_REQUESTS = 100000 |
| | | MAX_RETRIES = 3 |
| | | REQUEST_TIMEOUT = 60 |
| | | OUTPUT_DIR = './load_test_report' |
| | | |
| | | # --- 初始化 --- |
| | | dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET) |
| | | |
| | | LARGE_CONTENT = "备注压测中" * 10 |
| | | FILES_PATH = "/userfiles/1463828311460319233/程序附件//detection/virusdetection/virusDetection/2025/10/cs(2).jpg" |
| | | |
| | | |
| | | def create_animal_data(idx: int): |
| | | random_code = RandomUtil.generate_random_number_string(0, 10000) |
| | | random_code_grade = RandomUtil.generate_random_number_string(1, 2) |
| | | random_code_lx = RandomUtil.generate_random_number_string(0, 3) |
| | | random_date = RandomUtil.generate_random_date("2023-01-01", "2025-10-16") |
| | | return { |
| | | "id": "", |
| | | "animal": { |
| | | "id": "1978847173303963649" |
| | | }, |
| | | "variety": "食蟹猴", |
| | | "gender": "1", |
| | | "age": 1.9, |
| | | "weight": 8, |
| | | "project": { |
| | | "id": "1825457771270230018" |
| | | }, |
| | | "checkTime": f"{random_date} 09:11:38", |
| | | "collectTime": f"{random_date} 09:12:03", |
| | | "moduleKey": "", |
| | | "type": random_code_grade, |
| | | "monkeyTLymphocyte": random_code_lx, |
| | | "spv": random_code_lx, |
| | | "siv": random_code_lx, |
| | | "measlesVirusMv": random_code_lx, |
| | | "bvirus": random_code_lx, |
| | | "srv": random_code_lx, |
| | | "fileUrl": FILES_PATH, |
| | | "remarks": LARGE_CONTENT, |
| | | "purpose": "2", |
| | | "version": "", |
| | | "grade": "2" |
| | | } |
| | | |
| | | |
| | | async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES): |
| | | attempt = 0 |
| | | last_err = None |
| | | while attempt < max_retries: |
| | | data = create_animal_data(index) |
| | | start = time.time() |
| | | try: |
| | | async with session.post(url, json=data, headers=headers) as resp: |
| | | text = await resp.text() |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | status = resp.status |
| | | if status == 200: |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': status, |
| | | 'latency_ms': latency_ms, |
| | | 'response_size': len(text) if text is not None else None, |
| | | 'error': None |
| | | } |
| | | else: |
| | | last_err = f'status_{status}:{text}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | except Exception as e: |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | last_err = f'{type(e).__name__}:{str(e)}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | # 最终失败 |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': 0, |
| | | 'latency_ms': latency_ms if 'latency_ms' in locals() else 0, |
| | | 'response_size': None, |
| | | 'error': last_err |
| | | } |
| | | |
| | | |
| | | async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock): |
| | | while True: |
| | | idx = await queue.get() |
| | | if idx is None: |
| | | queue.task_done() |
| | | break |
| | | try: |
| | | res = await perform_request(session, idx) |
| | | # 记录到报告生成器 |
| | | gen.record_result( |
| | | index=res['index'], |
| | | timestamp=res['timestamp'], |
| | | status_code=int(res['status_code']), |
| | | latency_ms=float(res['latency_ms']), |
| | | response_size=res.get('response_size'), |
| | | error=res.get('error') |
| | | ) |
| | | async with lock: |
| | | if res['status_code'] and 200 <= res['status_code'] < 300: |
| | | success_counter['count'] += 1 |
| | | else: |
| | | failed_list.append((res['index'], res.get('error'))) |
| | | pbar.update(1) |
| | | except Exception as e: |
| | | async with lock: |
| | | failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}')) |
| | | pbar.update(1) |
| | | finally: |
| | | queue.task_done() |
| | | |
| | | |
| | | async def batch_create_animals(total: int, num_workers: int): |
| | | # 动态加载报告生成器模块(支持中文文件名) |
| | | gen = None |
| | | try: |
| | | import importlib.util |
| | | script_dir = os.path.dirname(os.path.abspath(__file__)) |
| | | report_path = os.path.join(script_dir, 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py') |
| | | if os.path.exists(report_path): |
| | | spec = importlib.util.spec_from_file_location('report_module', report_path) |
| | | report_module = importlib.util.module_from_spec(spec) |
| | | spec.loader.exec_module(report_module) |
| | | LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator') |
| | | else: |
| | | # 备用:尝试直接导入模块名(若你的文件名已改为 ascii) |
| | | from report_generator import LoadTestReportGenerator # type: ignore |
| | | gen = LoadTestReportGenerator(test_name=f'{apiname}压测任务', report_title='压测详细报告') |
| | | except Exception as e: |
| | | print('无法加载压测报告生成器,请确认stress_test_report_generator.py 文件位置正确。\n', e) |
| | | raise |
| | | |
| | | timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT) |
| | | connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False) |
| | | async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session: |
| | | queue = asyncio.Queue() |
| | | for i in range(1, total + 1): |
| | | await queue.put(i) |
| | | for _ in range(num_workers): |
| | | await queue.put(None) |
| | | |
| | | success_counter = {'count': 0} |
| | | failed_list = [] |
| | | lock = asyncio.Lock() |
| | | |
| | | with tqdm(total=total, desc='创建进度') as pbar: |
| | | workers = [ |
| | | asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock)) |
| | | for i in range(num_workers) |
| | | ] |
| | | await asyncio.gather(*workers) |
| | | |
| | | # 任务完成,生成报告 |
| | | os.makedirs(OUTPUT_DIR, exist_ok=True) |
| | | outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx']) |
| | | |
| | | stats = gen.compute_stats() |
| | | |
| | | # 构造钉钉摘要消息(中文) |
| | | now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
| | | msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}'] |
| | | msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}") |
| | | msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}") |
| | | lat = stats.get('latency_ms', {}) |
| | | msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}") |
| | | |
| | | # 列出生成的报告文件 |
| | | file_list = [] |
| | | for k, v in outputs.items(): |
| | | if k == 'charts': |
| | | for cname, cpath in v.items(): |
| | | file_list.append(os.path.abspath(cpath)) |
| | | else: |
| | | file_list.append(os.path.abspath(v)) |
| | | msg.append('生成文件:') |
| | | for p in file_list: |
| | | msg.append(p) |
| | | |
| | | final_msg = '\n'.join(msg) |
| | | |
| | | # 发送钉钉消息 |
| | | try: |
| | | dingtalk_helper.send_message(final_msg) |
| | | except Exception as e: |
| | | print('发送钉钉消息失败:', e) |
| | | |
| | | print('\n[SUMMARY] 已生成报告并发送钉钉摘要。') |
| | | print('成功数:', success_counter['count'], ' 失败数:', len(failed_list)) |
| | | if failed_list: |
| | | print('失败示例(最多显示50条):') |
| | | for idx, err in failed_list[:50]: |
| | | print(f' #{idx} => {err}') |
| | | |
| | | |
| | | if __name__ == '__main__': |
| | | # 运行前建议先用小规模测试 |
| | | TOTAL_REQUESTS = TOTAL_REQUESTS |
| | | NUM_WORKERS = NUM_WORKERS |
| | | asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS)) |
| New file |
| | |
| | | index,timestamp,datetime,status_code,latency_ms,response_size,error |
| | | 6,1760579736.5128722,2025-10-16 09:55:36,200,715.6155109405518,20, |
| | | 1,1760579736.528873,2025-10-16 09:55:36,200,738.6245727539062,20, |
| | | 8,1760579736.8814845,2025-10-16 09:55:36,200,1082.2265148162842,20, |
| | | 7,1760579737.034483,2025-10-16 09:55:37,200,1236.2267971038818,20, |
| | | 5,1760579737.072551,2025-10-16 09:55:37,200,1276.2954235076904,20, |
| | | 19,1760579737.0930712,2025-10-16 09:55:37,200,1285.822868347168,20, |
| | | 2,1760579737.28608,2025-10-16 09:55:37,200,1493.8290119171143,20, |
| | | 18,1760579737.4374523,2025-10-16 09:55:37,200,1631.2031745910645,20, |
| | | 3,1760579737.4674568,2025-10-16 09:55:37,200,1674.2029190063477,20, |
| | | 10,1760579737.5387669,2025-10-16 09:55:37,200,1737.511157989502,20, |
| | | 4,1760579737.569851,2025-10-16 09:55:37,200,1775.5978107452393,20, |
| | | 14,1760579737.6775362,2025-10-16 09:55:37,200,1873.2872009277344,20, |
| | | 44,1760579737.8772526,2025-10-16 09:55:37,200,2055.0029277801514,20, |
| | | 52,1760579738.0004578,2025-10-16 09:55:38,200,2174.2074489593506,20, |
| | | 40,1760579738.0458317,2025-10-16 09:55:38,200,2226.5822887420654,20, |
| | | 36,1760579738.0685692,2025-10-16 09:55:38,200,2251.3201236724854,20, |
| | | 16,1760579738.1789966,2025-10-16 09:55:38,200,2373.7473487854004,20, |
| | | 54,1760579738.262952,2025-10-16 09:55:38,200,2433.703899383545,20, |
| | | 13,1760579738.4627268,2025-10-16 09:55:38,200,2659.477710723877,19, |
| | | 12,1760579738.469727,2025-10-16 09:55:38,200,2666.477918624878,20, |
| | | 37,1760579738.5605764,2025-10-16 09:55:38,200,2742.3269748687744,20, |
| | | 47,1760579738.608516,2025-10-16 09:55:38,200,2785.266637802124,20, |
| | | 58,1760579738.793892,2025-10-16 09:55:38,200,2962.644100189209,20, |
| | | 48,1760579738.8212566,2025-10-16 09:55:38,200,2997.007131576538,20, |
| | | 46,1760579738.9038382,2025-10-16 09:55:38,200,3080.5888175964355,20, |
| | | 26,1760579738.9655027,2025-10-16 09:55:38,200,3154.2539596557617,20, |
| | | 43,1760579739.062388,2025-10-16 09:55:39,200,3241.1389350891113,20, |
| | | 34,1760579739.149593,2025-10-16 09:55:39,200,3333.3449363708496,20, |
| | | 45,1760579739.3271666,2025-10-16 09:55:39,200,3504.9169063568115,20, |
| | | 41,1760579739.3321707,2025-10-16 09:55:39,200,3511.9237899780273,20, |
| | | 25,1760579739.344079,2025-10-16 09:55:39,200,3533.830165863037,20, |
| | | 51,1760579739.4308379,2025-10-16 09:55:39,200,3604.5875549316406,20, |
| | | 29,1760579739.536484,2025-10-16 09:55:39,200,3723.2346534729004,20, |
| | | 84,1760579739.630485,2025-10-16 09:55:39,200,3784.235954284668,20, |
| | | 20,1760579739.776465,2025-10-16 09:55:39,200,3969.2165851593018,20, |
| | | 15,1760579739.8412845,2025-10-16 09:55:39,200,4037.0354652404785,20, |
| | | 31,1760579739.8695781,2025-10-16 09:55:39,200,4055.328845977783,20, |
| | | 60,1760579739.892454,2025-10-16 09:55:39,200,4060.203790664673,20, |
| | | 27,1760579740.0023754,2025-10-16 09:55:40,200,4191.126585006714,20, |
| | | 30,1760579740.145428,2025-10-16 09:55:40,200,4332.178592681885,20, |
| | | 53,1760579740.1794794,2025-10-16 09:55:40,200,4351.231336593628,20, |
| | | 42,1760579740.366609,2025-10-16 09:55:40,200,4546.3621616363525,20, |
| | | 71,1760579740.366609,2025-10-16 09:55:40,200,4528.359413146973,20, |
| | | 75,1760579740.4744072,2025-10-16 09:55:40,200,4633.156538009644,20, |
| | | 66,1760579740.5180902,2025-10-16 09:55:40,200,4681.840181350708,20, |
| | | 62,1760579740.6459935,2025-10-16 09:55:40,200,4812.745571136475,20, |
| | | 64,1760579740.6745234,2025-10-16 09:55:40,200,4840.275526046753,20, |
| | | 72,1760579740.8360484,2025-10-16 09:55:40,200,4996.798753738403,20, |
| | | 33,1760579740.8478594,2025-10-16 09:55:40,200,5032.6104164123535,20, |
| | | 24,1760579740.961691,2025-10-16 09:55:40,200,5151.442050933838,20, |
| | | 39,1760579740.9853978,2025-10-16 09:55:40,200,5166.14842414856,20, |
| | | 92,1760579741.1095653,2025-10-16 09:55:41,200,5259.315013885498,20, |
| | | 95,1760579741.17415,2025-10-16 09:55:41,200,5321.899175643921,20, |
| | | 97,1760579741.2893555,2025-10-16 09:55:41,200,5436.105251312256,20, |
| | | 28,1760579741.3184445,2025-10-16 09:55:41,200,5506.195545196533,20, |
| | | 70,1760579741.4219213,2025-10-16 09:55:41,200,5583.671569824219,20, |
| | | 80,1760579741.4306922,2025-10-16 09:55:41,200,5587.444305419922,20, |
| | | 73,1760579741.5471199,2025-10-16 09:55:41,200,5707.870244979858,20, |
| | | 93,1760579741.6452277,2025-10-16 09:55:41,200,5793.977737426758,20, |
| | | 94,1760579741.7387617,2025-10-16 09:55:41,200,5887.511730194092,19, |
| | | 49,1760579741.79457,2025-10-16 09:55:41,200,5969.320774078369,20, |
| | | 89,1760579741.8575878,2025-10-16 09:55:41,200,6009.339809417725,20, |
| | | 32,1760579741.8911214,2025-10-16 09:55:41,200,6076.872110366821,20, |
| | | 38,1760579741.9916003,2025-10-16 09:55:41,200,6173.350811004639,20, |
| | | 96,1760579742.1457305,2025-10-16 09:55:42,200,6293.479681015015,20, |
| | | 98,1760579742.1986532,2025-10-16 09:55:42,200,6345.402956008911,20, |
| | | 76,1760579742.304511,2025-10-16 09:55:42,200,6463.2604122161865,20, |
| | | 81,1760579742.3438184,2025-10-16 09:55:42,200,6499.570608139038,20, |
| | | 77,1760579742.370017,2025-10-16 09:55:42,200,6527.769088745117,20, |
| | | 57,1760579742.4302893,2025-10-16 09:55:42,200,6599.04146194458,20, |
| | | 22,1760579742.6512027,2025-10-16 09:55:42,200,6842.953681945801,20, |
| | | 78,1760579742.6552012,2025-10-16 09:55:42,200,6812.953233718872,20, |
| | | 83,1760579742.7378995,2025-10-16 09:55:42,200,6892.650604248047,20, |
| | | 88,1760579742.771665,2025-10-16 09:55:42,200,6923.417091369629,20, |
| | | 99,1760579742.813827,2025-10-16 09:55:42,200,6959.575176239014,20, |
| | | 50,1760579742.8238244,2025-10-16 09:55:42,200,6998.575210571289,20, |
| | | 87,1760579743.0883355,2025-10-16 09:55:43,200,7241.0852909088135,20, |
| | | 82,1760579743.1234865,2025-10-16 09:55:43,200,7279.238700866699,20, |
| | | 21,1760579743.161384,2025-10-16 09:55:43,200,7353.135108947754,20, |
| | | 90,1760579743.1896164,2025-10-16 09:55:43,200,7340.368270874023,20, |
| | | 35,1760579743.1936219,2025-10-16 09:55:43,200,7377.373695373535,19, |
| | | 91,1760579743.2927885,2025-10-16 09:55:43,200,7443.540334701538,20, |
| | | 68,1760579743.5564194,2025-10-16 09:55:43,200,7719.170093536377,20, |
| | | 61,1760579743.6013098,2025-10-16 09:55:43,200,7768.061876296997,20, |
| | | 17,1760579743.6033225,2025-10-16 09:55:43,200,7798.073291778564,20, |
| | | 63,1760579743.611075,2025-10-16 09:55:43,200,7776.827096939087,20, |
| | | 9,1760579743.621174,2025-10-16 09:55:43,200,7819.918394088745,20, |
| | | 55,1760579743.7662518,2025-10-16 09:55:43,200,7937.003612518311,20, |
| | | 67,1760579743.999791,2025-10-16 09:55:43,200,8163.540840148926,20, |
| | | 85,1760579744.0067902,2025-10-16 09:55:44,200,8160.54105758667,20, |
| | | 100,1760579744.0268416,2025-10-16 09:55:44,200,8172.5897789001465,20, |
| | | 74,1760579744.0278468,2025-10-16 09:55:44,200,8187.59822845459,20, |
| | | 59,1760579744.0623982,2025-10-16 09:55:44,200,8230.148077011108,20, |
| | | 65,1760579744.2366185,2025-10-16 09:55:44,200,8401.370763778687,19, |
| | | 86,1760579744.4280767,2025-10-16 09:55:44,200,8580.8265209198,20, |
| | | 56,1760579744.434209,2025-10-16 09:55:44,200,8603.960990905762,20, |
| | | 23,1760579744.5166273,2025-10-16 09:55:44,200,8707.377433776855,20, |
| | | 11,1760579744.521632,2025-10-16 09:55:44,200,8719.382762908936,20, |
| | | 79,1760579744.5326326,2025-10-16 09:55:44,200,8689.384698867798,20, |
| | | 69,1760579744.7850199,2025-10-16 09:55:44,200,8947.770595550537,20, |
| New file |
| | |
| | | |
| | | <!doctype html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="utf-8"> |
| | | <title>压测详细报告</title> |
| | | <style> |
| | | body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px} |
| | | h2{color:#2c3e50} |
| | | table{border-collapse:collapse; width:100%} |
| | | th,td{padding:6px; text-align:left} |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>压测详细报告</h1> |
| | | |
| | | <h2>摘要</h2> |
| | | <ul> |
| | | <li>报告名称:压测详细报告</li> |
| | | <li>生成时间:2025-10-16 09:55:45</li> |
| | | <li>总请求数:100</li> |
| | | <li>成功数:100,失败数:0,成功率:100.00%</li> |
| | | <li>总耗时(秒):8.27</li> |
| | | <li>平均吞吐(req/s):12.09</li> |
| | | </ul> |
| | | |
| | | |
| | | <h2>响应时间统计 (ms)</h2> |
| | | <ul> |
| | | <li>最小:715.62</li> |
| | | <li>最大:8947.77</li> |
| | | <li>平均:5055.96</li> |
| | | <li>中位数(P50):5158.80</li> |
| | | <li>P90:8164.45,P95:8581.98,P99:8721.67</li> |
| | | </ul> |
| | | |
| | | <h2>状态码分布</h2><ul><li>200: 100</li></ul> |
| | | <h2>错误汇总</h2><p>无错误记录</p> |
| | | <h2>图表</h2><div><h3>latency_hist</h3><img src="压测任务_20251016_095544_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="压测任务_20251016_095544_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div> |
| | | |
| | | <h2>请求明细(仅显示前100条)</h2> |
| | | <table border="1" cellpadding="4" cellspacing="0"> |
| | | <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr> |
| | | <tr><td>6</td><td>2025-10-16 09:55:36</td><td>200</td><td>715.6155109405518</td><td>20</td><td></td></tr><tr><td>1</td><td>2025-10-16 09:55:36</td><td>200</td><td>738.6245727539062</td><td>20</td><td></td></tr><tr><td>8</td><td>2025-10-16 09:55:36</td><td>200</td><td>1082.2265148162842</td><td>20</td><td></td></tr><tr><td>7</td><td>2025-10-16 09:55:37</td><td>200</td><td>1236.2267971038818</td><td>20</td><td></td></tr><tr><td>5</td><td>2025-10-16 09:55:37</td><td>200</td><td>1276.2954235076904</td><td>20</td><td></td></tr><tr><td>19</td><td>2025-10-16 09:55:37</td><td>200</td><td>1285.822868347168</td><td>20</td><td></td></tr><tr><td>2</td><td>2025-10-16 09:55:37</td><td>200</td><td>1493.8290119171143</td><td>20</td><td></td></tr><tr><td>18</td><td>2025-10-16 09:55:37</td><td>200</td><td>1631.2031745910645</td><td>20</td><td></td></tr><tr><td>3</td><td>2025-10-16 09:55:37</td><td>200</td><td>1674.2029190063477</td><td>20</td><td></td></tr><tr><td>10</td><td>2025-10-16 09:55:37</td><td>200</td><td>1737.511157989502</td><td>20</td><td></td></tr><tr><td>4</td><td>2025-10-16 09:55:37</td><td>200</td><td>1775.5978107452393</td><td>20</td><td></td></tr><tr><td>14</td><td>2025-10-16 09:55:37</td><td>200</td><td>1873.2872009277344</td><td>20</td><td></td></tr><tr><td>44</td><td>2025-10-16 09:55:37</td><td>200</td><td>2055.0029277801514</td><td>20</td><td></td></tr><tr><td>52</td><td>2025-10-16 09:55:38</td><td>200</td><td>2174.2074489593506</td><td>20</td><td></td></tr><tr><td>40</td><td>2025-10-16 09:55:38</td><td>200</td><td>2226.5822887420654</td><td>20</td><td></td></tr><tr><td>36</td><td>2025-10-16 09:55:38</td><td>200</td><td>2251.3201236724854</td><td>20</td><td></td></tr><tr><td>16</td><td>2025-10-16 09:55:38</td><td>200</td><td>2373.7473487854004</td><td>20</td><td></td></tr><tr><td>54</td><td>2025-10-16 09:55:38</td><td>200</td><td>2433.703899383545</td><td>20</td><td></td></tr><tr><td>13</td><td>2025-10-16 09:55:38</td><td>200</td><td>2659.477710723877</td><td>19</td><td></td></tr><tr><td>12</td><td>2025-10-16 09:55:38</td><td>200</td><td>2666.477918624878</td><td>20</td><td></td></tr><tr><td>37</td><td>2025-10-16 09:55:38</td><td>200</td><td>2742.3269748687744</td><td>20</td><td></td></tr><tr><td>47</td><td>2025-10-16 09:55:38</td><td>200</td><td>2785.266637802124</td><td>20</td><td></td></tr><tr><td>58</td><td>2025-10-16 09:55:38</td><td>200</td><td>2962.644100189209</td><td>20</td><td></td></tr><tr><td>48</td><td>2025-10-16 09:55:38</td><td>200</td><td>2997.007131576538</td><td>20</td><td></td></tr><tr><td>46</td><td>2025-10-16 09:55:38</td><td>200</td><td>3080.5888175964355</td><td>20</td><td></td></tr><tr><td>26</td><td>2025-10-16 09:55:38</td><td>200</td><td>3154.2539596557617</td><td>20</td><td></td></tr><tr><td>43</td><td>2025-10-16 09:55:39</td><td>200</td><td>3241.1389350891113</td><td>20</td><td></td></tr><tr><td>34</td><td>2025-10-16 09:55:39</td><td>200</td><td>3333.3449363708496</td><td>20</td><td></td></tr><tr><td>45</td><td>2025-10-16 09:55:39</td><td>200</td><td>3504.9169063568115</td><td>20</td><td></td></tr><tr><td>41</td><td>2025-10-16 09:55:39</td><td>200</td><td>3511.9237899780273</td><td>20</td><td></td></tr><tr><td>25</td><td>2025-10-16 09:55:39</td><td>200</td><td>3533.830165863037</td><td>20</td><td></td></tr><tr><td>51</td><td>2025-10-16 09:55:39</td><td>200</td><td>3604.5875549316406</td><td>20</td><td></td></tr><tr><td>29</td><td>2025-10-16 09:55:39</td><td>200</td><td>3723.2346534729004</td><td>20</td><td></td></tr><tr><td>84</td><td>2025-10-16 09:55:39</td><td>200</td><td>3784.235954284668</td><td>20</td><td></td></tr><tr><td>20</td><td>2025-10-16 09:55:39</td><td>200</td><td>3969.2165851593018</td><td>20</td><td></td></tr><tr><td>15</td><td>2025-10-16 09:55:39</td><td>200</td><td>4037.0354652404785</td><td>20</td><td></td></tr><tr><td>31</td><td>2025-10-16 09:55:39</td><td>200</td><td>4055.328845977783</td><td>20</td><td></td></tr><tr><td>60</td><td>2025-10-16 09:55:39</td><td>200</td><td>4060.203790664673</td><td>20</td><td></td></tr><tr><td>27</td><td>2025-10-16 09:55:40</td><td>200</td><td>4191.126585006714</td><td>20</td><td></td></tr><tr><td>30</td><td>2025-10-16 09:55:40</td><td>200</td><td>4332.178592681885</td><td>20</td><td></td></tr><tr><td>53</td><td>2025-10-16 09:55:40</td><td>200</td><td>4351.231336593628</td><td>20</td><td></td></tr><tr><td>42</td><td>2025-10-16 09:55:40</td><td>200</td><td>4546.3621616363525</td><td>20</td><td></td></tr><tr><td>71</td><td>2025-10-16 09:55:40</td><td>200</td><td>4528.359413146973</td><td>20</td><td></td></tr><tr><td>75</td><td>2025-10-16 09:55:40</td><td>200</td><td>4633.156538009644</td><td>20</td><td></td></tr><tr><td>66</td><td>2025-10-16 09:55:40</td><td>200</td><td>4681.840181350708</td><td>20</td><td></td></tr><tr><td>62</td><td>2025-10-16 09:55:40</td><td>200</td><td>4812.745571136475</td><td>20</td><td></td></tr><tr><td>64</td><td>2025-10-16 09:55:40</td><td>200</td><td>4840.275526046753</td><td>20</td><td></td></tr><tr><td>72</td><td>2025-10-16 09:55:40</td><td>200</td><td>4996.798753738403</td><td>20</td><td></td></tr><tr><td>33</td><td>2025-10-16 09:55:40</td><td>200</td><td>5032.6104164123535</td><td>20</td><td></td></tr><tr><td>24</td><td>2025-10-16 09:55:40</td><td>200</td><td>5151.442050933838</td><td>20</td><td></td></tr><tr><td>39</td><td>2025-10-16 09:55:40</td><td>200</td><td>5166.14842414856</td><td>20</td><td></td></tr><tr><td>92</td><td>2025-10-16 09:55:41</td><td>200</td><td>5259.315013885498</td><td>20</td><td></td></tr><tr><td>95</td><td>2025-10-16 09:55:41</td><td>200</td><td>5321.899175643921</td><td>20</td><td></td></tr><tr><td>97</td><td>2025-10-16 09:55:41</td><td>200</td><td>5436.105251312256</td><td>20</td><td></td></tr><tr><td>28</td><td>2025-10-16 09:55:41</td><td>200</td><td>5506.195545196533</td><td>20</td><td></td></tr><tr><td>70</td><td>2025-10-16 09:55:41</td><td>200</td><td>5583.671569824219</td><td>20</td><td></td></tr><tr><td>80</td><td>2025-10-16 09:55:41</td><td>200</td><td>5587.444305419922</td><td>20</td><td></td></tr><tr><td>73</td><td>2025-10-16 09:55:41</td><td>200</td><td>5707.870244979858</td><td>20</td><td></td></tr><tr><td>93</td><td>2025-10-16 09:55:41</td><td>200</td><td>5793.977737426758</td><td>20</td><td></td></tr><tr><td>94</td><td>2025-10-16 09:55:41</td><td>200</td><td>5887.511730194092</td><td>19</td><td></td></tr><tr><td>49</td><td>2025-10-16 09:55:41</td><td>200</td><td>5969.320774078369</td><td>20</td><td></td></tr><tr><td>89</td><td>2025-10-16 09:55:41</td><td>200</td><td>6009.339809417725</td><td>20</td><td></td></tr><tr><td>32</td><td>2025-10-16 09:55:41</td><td>200</td><td>6076.872110366821</td><td>20</td><td></td></tr><tr><td>38</td><td>2025-10-16 09:55:41</td><td>200</td><td>6173.350811004639</td><td>20</td><td></td></tr><tr><td>96</td><td>2025-10-16 09:55:42</td><td>200</td><td>6293.479681015015</td><td>20</td><td></td></tr><tr><td>98</td><td>2025-10-16 09:55:42</td><td>200</td><td>6345.402956008911</td><td>20</td><td></td></tr><tr><td>76</td><td>2025-10-16 09:55:42</td><td>200</td><td>6463.2604122161865</td><td>20</td><td></td></tr><tr><td>81</td><td>2025-10-16 09:55:42</td><td>200</td><td>6499.570608139038</td><td>20</td><td></td></tr><tr><td>77</td><td>2025-10-16 09:55:42</td><td>200</td><td>6527.769088745117</td><td>20</td><td></td></tr><tr><td>57</td><td>2025-10-16 09:55:42</td><td>200</td><td>6599.04146194458</td><td>20</td><td></td></tr><tr><td>22</td><td>2025-10-16 09:55:42</td><td>200</td><td>6842.953681945801</td><td>20</td><td></td></tr><tr><td>78</td><td>2025-10-16 09:55:42</td><td>200</td><td>6812.953233718872</td><td>20</td><td></td></tr><tr><td>83</td><td>2025-10-16 09:55:42</td><td>200</td><td>6892.650604248047</td><td>20</td><td></td></tr><tr><td>88</td><td>2025-10-16 09:55:42</td><td>200</td><td>6923.417091369629</td><td>20</td><td></td></tr><tr><td>99</td><td>2025-10-16 09:55:42</td><td>200</td><td>6959.575176239014</td><td>20</td><td></td></tr><tr><td>50</td><td>2025-10-16 09:55:42</td><td>200</td><td>6998.575210571289</td><td>20</td><td></td></tr><tr><td>87</td><td>2025-10-16 09:55:43</td><td>200</td><td>7241.0852909088135</td><td>20</td><td></td></tr><tr><td>82</td><td>2025-10-16 09:55:43</td><td>200</td><td>7279.238700866699</td><td>20</td><td></td></tr><tr><td>21</td><td>2025-10-16 09:55:43</td><td>200</td><td>7353.135108947754</td><td>20</td><td></td></tr><tr><td>90</td><td>2025-10-16 09:55:43</td><td>200</td><td>7340.368270874023</td><td>20</td><td></td></tr><tr><td>35</td><td>2025-10-16 09:55:43</td><td>200</td><td>7377.373695373535</td><td>19</td><td></td></tr><tr><td>91</td><td>2025-10-16 09:55:43</td><td>200</td><td>7443.540334701538</td><td>20</td><td></td></tr><tr><td>68</td><td>2025-10-16 09:55:43</td><td>200</td><td>7719.170093536377</td><td>20</td><td></td></tr><tr><td>61</td><td>2025-10-16 09:55:43</td><td>200</td><td>7768.061876296997</td><td>20</td><td></td></tr><tr><td>17</td><td>2025-10-16 09:55:43</td><td>200</td><td>7798.073291778564</td><td>20</td><td></td></tr><tr><td>63</td><td>2025-10-16 09:55:43</td><td>200</td><td>7776.827096939087</td><td>20</td><td></td></tr><tr><td>9</td><td>2025-10-16 09:55:43</td><td>200</td><td>7819.918394088745</td><td>20</td><td></td></tr><tr><td>55</td><td>2025-10-16 09:55:43</td><td>200</td><td>7937.003612518311</td><td>20</td><td></td></tr><tr><td>67</td><td>2025-10-16 09:55:43</td><td>200</td><td>8163.540840148926</td><td>20</td><td></td></tr><tr><td>85</td><td>2025-10-16 09:55:44</td><td>200</td><td>8160.54105758667</td><td>20</td><td></td></tr><tr><td>100</td><td>2025-10-16 09:55:44</td><td>200</td><td>8172.5897789001465</td><td>20</td><td></td></tr><tr><td>74</td><td>2025-10-16 09:55:44</td><td>200</td><td>8187.59822845459</td><td>20</td><td></td></tr><tr><td>59</td><td>2025-10-16 09:55:44</td><td>200</td><td>8230.148077011108</td><td>20</td><td></td></tr><tr><td>65</td><td>2025-10-16 09:55:44</td><td>200</td><td>8401.370763778687</td><td>19</td><td></td></tr><tr><td>86</td><td>2025-10-16 09:55:44</td><td>200</td><td>8580.8265209198</td><td>20</td><td></td></tr><tr><td>56</td><td>2025-10-16 09:55:44</td><td>200</td><td>8603.960990905762</td><td>20</td><td></td></tr><tr><td>23</td><td>2025-10-16 09:55:44</td><td>200</td><td>8707.377433776855</td><td>20</td><td></td></tr><tr><td>11</td><td>2025-10-16 09:55:44</td><td>200</td><td>8719.382762908936</td><td>20</td><td></td></tr><tr><td>79</td><td>2025-10-16 09:55:44</td><td>200</td><td>8689.384698867798</td><td>20</td><td></td></tr><tr><td>69</td><td>2025-10-16 09:55:44</td><td>200</td><td>8947.770595550537</td><td>20</td><td></td></tr> |
| | | </table> |
| | | |
| | | <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p> |
| | | </body> |
| | | </html> |
| | | |
| New file |
| | |
| | | { |
| | | "stats": { |
| | | "total_requests": 100, |
| | | "success_count": 100, |
| | | "fail_count": 0, |
| | | "success_rate": 1.0, |
| | | "duration_seconds": 8.27214765548706, |
| | | "throughput_rps": 12.088759070163388, |
| | | "latency_ms": { |
| | | "min": 715.6155109405518, |
| | | "max": 8947.770595550537, |
| | | "avg": 5055.96134185791, |
| | | "median": 5158.795237541199, |
| | | "p90": 8164.445734024048, |
| | | "p95": 8581.983244419098, |
| | | "p99": 8721.666641235353 |
| | | }, |
| | | "status_groups": { |
| | | "200": 100 |
| | | }, |
| | | "error_summary": {}, |
| | | "rps_series": [ |
| | | [ |
| | | 1760579736, |
| | | 3 |
| | | ], |
| | | [ |
| | | 1760579737, |
| | | 10 |
| | | ], |
| | | [ |
| | | 1760579738, |
| | | 13 |
| | | ], |
| | | [ |
| | | 1760579739, |
| | | 12 |
| | | ], |
| | | [ |
| | | 1760579740, |
| | | 13 |
| | | ], |
| | | [ |
| | | 1760579741, |
| | | 13 |
| | | ], |
| | | [ |
| | | 1760579742, |
| | | 12 |
| | | ], |
| | | [ |
| | | 1760579743, |
| | | 13 |
| | | ], |
| | | [ |
| | | 1760579744, |
| | | 11 |
| | | ] |
| | | ] |
| | | }, |
| | | "records": [ |
| | | { |
| | | "index": 6, |
| | | "timestamp": 1760579736.5128722, |
| | | "datetime": "2025-10-16 09:55:36", |
| | | "status_code": 200, |
| | | "latency_ms": 715.6155109405518, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 1, |
| | | "timestamp": 1760579736.528873, |
| | | "datetime": "2025-10-16 09:55:36", |
| | | "status_code": 200, |
| | | "latency_ms": 738.6245727539062, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 8, |
| | | "timestamp": 1760579736.8814845, |
| | | "datetime": "2025-10-16 09:55:36", |
| | | "status_code": 200, |
| | | "latency_ms": 1082.2265148162842, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 7, |
| | | "timestamp": 1760579737.034483, |
| | | "datetime": "2025-10-16 09:55:37", |
| | | "status_code": 200, |
| | | "latency_ms": 1236.2267971038818, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 5, |
| | | "timestamp": 1760579737.072551, |
| | | "datetime": "2025-10-16 09:55:37", |
| | | "status_code": 200, |
| | | "latency_ms": 1276.2954235076904, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 19, |
| | | "timestamp": 1760579737.0930712, |
| | | "datetime": "2025-10-16 09:55:37", |
| | | "status_code": 200, |
| | | "latency_ms": 1285.822868347168, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 2, |
| | | "timestamp": 1760579737.28608, |
| | | "datetime": "2025-10-16 09:55:37", |
| | | "status_code": 200, |
| | | "latency_ms": 1493.8290119171143, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 18, |
| | | "timestamp": 1760579737.4374523, |
| | | "datetime": "2025-10-16 09:55:37", |
| | | "status_code": 200, |
| | | "latency_ms": 1631.2031745910645, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 3, |
| | | "timestamp": 1760579737.4674568, |
| | | "datetime": "2025-10-16 09:55:37", |
| | | "status_code": 200, |
| | | "latency_ms": 1674.2029190063477, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 10, |
| | | "timestamp": 1760579737.5387669, |
| | | "datetime": "2025-10-16 09:55:37", |
| | | "status_code": 200, |
| | | "latency_ms": 1737.511157989502, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 4, |
| | | "timestamp": 1760579737.569851, |
| | | "datetime": "2025-10-16 09:55:37", |
| | | "status_code": 200, |
| | | "latency_ms": 1775.5978107452393, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 14, |
| | | "timestamp": 1760579737.6775362, |
| | | "datetime": "2025-10-16 09:55:37", |
| | | "status_code": 200, |
| | | "latency_ms": 1873.2872009277344, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 44, |
| | | "timestamp": 1760579737.8772526, |
| | | "datetime": "2025-10-16 09:55:37", |
| | | "status_code": 200, |
| | | "latency_ms": 2055.0029277801514, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 52, |
| | | "timestamp": 1760579738.0004578, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 2174.2074489593506, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 40, |
| | | "timestamp": 1760579738.0458317, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 2226.5822887420654, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 36, |
| | | "timestamp": 1760579738.0685692, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 2251.3201236724854, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 16, |
| | | "timestamp": 1760579738.1789966, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 2373.7473487854004, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 54, |
| | | "timestamp": 1760579738.262952, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 2433.703899383545, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 13, |
| | | "timestamp": 1760579738.4627268, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 2659.477710723877, |
| | | "response_size": 19, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 12, |
| | | "timestamp": 1760579738.469727, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 2666.477918624878, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 37, |
| | | "timestamp": 1760579738.5605764, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 2742.3269748687744, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 47, |
| | | "timestamp": 1760579738.608516, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 2785.266637802124, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 58, |
| | | "timestamp": 1760579738.793892, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 2962.644100189209, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 48, |
| | | "timestamp": 1760579738.8212566, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 2997.007131576538, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 46, |
| | | "timestamp": 1760579738.9038382, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 3080.5888175964355, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 26, |
| | | "timestamp": 1760579738.9655027, |
| | | "datetime": "2025-10-16 09:55:38", |
| | | "status_code": 200, |
| | | "latency_ms": 3154.2539596557617, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 43, |
| | | "timestamp": 1760579739.062388, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 3241.1389350891113, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 34, |
| | | "timestamp": 1760579739.149593, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 3333.3449363708496, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 45, |
| | | "timestamp": 1760579739.3271666, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 3504.9169063568115, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 41, |
| | | "timestamp": 1760579739.3321707, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 3511.9237899780273, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 25, |
| | | "timestamp": 1760579739.344079, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 3533.830165863037, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 51, |
| | | "timestamp": 1760579739.4308379, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 3604.5875549316406, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 29, |
| | | "timestamp": 1760579739.536484, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 3723.2346534729004, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 84, |
| | | "timestamp": 1760579739.630485, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 3784.235954284668, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 20, |
| | | "timestamp": 1760579739.776465, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 3969.2165851593018, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 15, |
| | | "timestamp": 1760579739.8412845, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 4037.0354652404785, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 31, |
| | | "timestamp": 1760579739.8695781, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 4055.328845977783, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 60, |
| | | "timestamp": 1760579739.892454, |
| | | "datetime": "2025-10-16 09:55:39", |
| | | "status_code": 200, |
| | | "latency_ms": 4060.203790664673, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 27, |
| | | "timestamp": 1760579740.0023754, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 4191.126585006714, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 30, |
| | | "timestamp": 1760579740.145428, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 4332.178592681885, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 53, |
| | | "timestamp": 1760579740.1794794, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 4351.231336593628, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 42, |
| | | "timestamp": 1760579740.366609, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 4546.3621616363525, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 71, |
| | | "timestamp": 1760579740.366609, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 4528.359413146973, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 75, |
| | | "timestamp": 1760579740.4744072, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 4633.156538009644, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 66, |
| | | "timestamp": 1760579740.5180902, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 4681.840181350708, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 62, |
| | | "timestamp": 1760579740.6459935, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 4812.745571136475, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 64, |
| | | "timestamp": 1760579740.6745234, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 4840.275526046753, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 72, |
| | | "timestamp": 1760579740.8360484, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 4996.798753738403, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 33, |
| | | "timestamp": 1760579740.8478594, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 5032.6104164123535, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 24, |
| | | "timestamp": 1760579740.961691, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 5151.442050933838, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 39, |
| | | "timestamp": 1760579740.9853978, |
| | | "datetime": "2025-10-16 09:55:40", |
| | | "status_code": 200, |
| | | "latency_ms": 5166.14842414856, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 92, |
| | | "timestamp": 1760579741.1095653, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 5259.315013885498, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 95, |
| | | "timestamp": 1760579741.17415, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 5321.899175643921, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 97, |
| | | "timestamp": 1760579741.2893555, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 5436.105251312256, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 28, |
| | | "timestamp": 1760579741.3184445, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 5506.195545196533, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 70, |
| | | "timestamp": 1760579741.4219213, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 5583.671569824219, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 80, |
| | | "timestamp": 1760579741.4306922, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 5587.444305419922, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 73, |
| | | "timestamp": 1760579741.5471199, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 5707.870244979858, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 93, |
| | | "timestamp": 1760579741.6452277, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 5793.977737426758, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 94, |
| | | "timestamp": 1760579741.7387617, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 5887.511730194092, |
| | | "response_size": 19, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 49, |
| | | "timestamp": 1760579741.79457, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 5969.320774078369, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 89, |
| | | "timestamp": 1760579741.8575878, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 6009.339809417725, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 32, |
| | | "timestamp": 1760579741.8911214, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 6076.872110366821, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 38, |
| | | "timestamp": 1760579741.9916003, |
| | | "datetime": "2025-10-16 09:55:41", |
| | | "status_code": 200, |
| | | "latency_ms": 6173.350811004639, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 96, |
| | | "timestamp": 1760579742.1457305, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6293.479681015015, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 98, |
| | | "timestamp": 1760579742.1986532, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6345.402956008911, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 76, |
| | | "timestamp": 1760579742.304511, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6463.2604122161865, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 81, |
| | | "timestamp": 1760579742.3438184, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6499.570608139038, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 77, |
| | | "timestamp": 1760579742.370017, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6527.769088745117, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 57, |
| | | "timestamp": 1760579742.4302893, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6599.04146194458, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 22, |
| | | "timestamp": 1760579742.6512027, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6842.953681945801, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 78, |
| | | "timestamp": 1760579742.6552012, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6812.953233718872, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 83, |
| | | "timestamp": 1760579742.7378995, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6892.650604248047, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 88, |
| | | "timestamp": 1760579742.771665, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6923.417091369629, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 99, |
| | | "timestamp": 1760579742.813827, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6959.575176239014, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 50, |
| | | "timestamp": 1760579742.8238244, |
| | | "datetime": "2025-10-16 09:55:42", |
| | | "status_code": 200, |
| | | "latency_ms": 6998.575210571289, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 87, |
| | | "timestamp": 1760579743.0883355, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7241.0852909088135, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 82, |
| | | "timestamp": 1760579743.1234865, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7279.238700866699, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 21, |
| | | "timestamp": 1760579743.161384, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7353.135108947754, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 90, |
| | | "timestamp": 1760579743.1896164, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7340.368270874023, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 35, |
| | | "timestamp": 1760579743.1936219, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7377.373695373535, |
| | | "response_size": 19, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 91, |
| | | "timestamp": 1760579743.2927885, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7443.540334701538, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 68, |
| | | "timestamp": 1760579743.5564194, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7719.170093536377, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 61, |
| | | "timestamp": 1760579743.6013098, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7768.061876296997, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 17, |
| | | "timestamp": 1760579743.6033225, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7798.073291778564, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 63, |
| | | "timestamp": 1760579743.611075, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7776.827096939087, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 9, |
| | | "timestamp": 1760579743.621174, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7819.918394088745, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 55, |
| | | "timestamp": 1760579743.7662518, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 7937.003612518311, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 67, |
| | | "timestamp": 1760579743.999791, |
| | | "datetime": "2025-10-16 09:55:43", |
| | | "status_code": 200, |
| | | "latency_ms": 8163.540840148926, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 85, |
| | | "timestamp": 1760579744.0067902, |
| | | "datetime": "2025-10-16 09:55:44", |
| | | "status_code": 200, |
| | | "latency_ms": 8160.54105758667, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 100, |
| | | "timestamp": 1760579744.0268416, |
| | | "datetime": "2025-10-16 09:55:44", |
| | | "status_code": 200, |
| | | "latency_ms": 8172.5897789001465, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 74, |
| | | "timestamp": 1760579744.0278468, |
| | | "datetime": "2025-10-16 09:55:44", |
| | | "status_code": 200, |
| | | "latency_ms": 8187.59822845459, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 59, |
| | | "timestamp": 1760579744.0623982, |
| | | "datetime": "2025-10-16 09:55:44", |
| | | "status_code": 200, |
| | | "latency_ms": 8230.148077011108, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 65, |
| | | "timestamp": 1760579744.2366185, |
| | | "datetime": "2025-10-16 09:55:44", |
| | | "status_code": 200, |
| | | "latency_ms": 8401.370763778687, |
| | | "response_size": 19, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 86, |
| | | "timestamp": 1760579744.4280767, |
| | | "datetime": "2025-10-16 09:55:44", |
| | | "status_code": 200, |
| | | "latency_ms": 8580.8265209198, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 56, |
| | | "timestamp": 1760579744.434209, |
| | | "datetime": "2025-10-16 09:55:44", |
| | | "status_code": 200, |
| | | "latency_ms": 8603.960990905762, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 23, |
| | | "timestamp": 1760579744.5166273, |
| | | "datetime": "2025-10-16 09:55:44", |
| | | "status_code": 200, |
| | | "latency_ms": 8707.377433776855, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 11, |
| | | "timestamp": 1760579744.521632, |
| | | "datetime": "2025-10-16 09:55:44", |
| | | "status_code": 200, |
| | | "latency_ms": 8719.382762908936, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 79, |
| | | "timestamp": 1760579744.5326326, |
| | | "datetime": "2025-10-16 09:55:44", |
| | | "status_code": 200, |
| | | "latency_ms": 8689.384698867798, |
| | | "response_size": 20, |
| | | "error": null |
| | | }, |
| | | { |
| | | "index": 69, |
| | | "timestamp": 1760579744.7850199, |
| | | "datetime": "2025-10-16 09:55:44", |
| | | "status_code": 200, |
| | | "latency_ms": 8947.770595550537, |
| | | "response_size": 20, |
| | | "error": null |
| | | } |
| | | ] |
| | | } |
| New file |
| | |
| | | import asyncio |
| | | import aiohttp |
| | | import traceback |
| | | import time |
| | | from Util.random_util import RandomUtil # 保证该模块中提供了生成随机数字和随机日期的函数 |
| | | from Util.dingtalk_helper import DingTalkHelper |
| | | from tqdm import tqdm # 导入进度条库 |
| | | |
| | | # 钉钉机器人 access_token 和 secret |
| | | ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d' |
| | | SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19' |
| | | |
| | | # 请求URL和请求头 |
| | | apiname = "设备维保通知" |
| | | url = "http://192.168.6.168:5537/api/notify/save" |
| | | headers = { |
| | | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjA2MDczMTAsInVzZXJuYW1lIjoiZ2x5In0.3DC4-4oUMuIpSDT-dYn8bv9JvuPadIssUQfIKAnjAL4", |
| | | "Content-Type": "application/json" |
| | | } |
| | | |
| | | dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET) |
| | | |
| | | gender = "1" |
| | | mother_id = "" |
| | | mother_wholeCode = "" |
| | | father_id = "" |
| | | father_wholeCode = "" |
| | | algebra = 1 |
| | | preCode = "性能测试" |
| | | |
| | | # 生成请求体 |
| | | def create_animal_data(): |
| | | random_code = RandomUtil.generate_random_number_string(0, 999999999) |
| | | # random_date = RandomUtil.generate_random_date("2023-01-01", "2025-03-25") |
| | | return { |
| | | "id": "", |
| | | "type": "6", |
| | | "title": f"压测中{random_code}", |
| | | "content": ( |
| | | "压测中" * 500 |
| | | ), |
| | | "files": "/userfiles/1588133301094375425/程序附件/notify/notify/2025/10/15/173933/cs.jpg", |
| | | "status": "1", |
| | | "notifyRecordIds": "", |
| | | "isNotice": "0", |
| | | "receivingRange": "1" |
| | | } |
| | | |
| | | |
| | | # 异步发送单个请求,并返回是否成功 |
| | | async def create_animal(session: aiohttp.ClientSession, index: int, max_retries: int = 3): |
| | | data = create_animal_data() |
| | | attempt = 0 |
| | | while attempt < max_retries: |
| | | try: |
| | | async with session.post(url, json=data, headers=headers) as response: |
| | | resp_text = await response.text() |
| | | if response.status == 200: |
| | | print(f"[INFO] 第 {index} 个{apiname}创建成功,响应:{resp_text}") |
| | | return True |
| | | else: |
| | | print(f"[ERROR] 第 {index} 个{apiname}创建失败,状态码:{response.status},响应:{resp_text}") |
| | | return False |
| | | except Exception as e: |
| | | attempt += 1 |
| | | status_code = getattr(e, 'status', 'N/A') |
| | | error_trace = traceback.format_exc() |
| | | full_log = ( |
| | | f"请求URL: {url}\n" |
| | | f"请求头: {headers}\n" |
| | | f"请求数据: {data}\n" |
| | | f"状态码: {status_code}\n" |
| | | f"异常信息: {str(e)}\n" |
| | | f"堆栈跟踪:\n{error_trace}\n" |
| | | f"重试次数: {attempt}/{max_retries}" |
| | | ) |
| | | print(f"[EXCEPTION] 第 {index} 个{apiname}请求异常:\n{full_log}\n") |
| | | if attempt < max_retries: |
| | | # 重试前等待 1 秒 |
| | | await asyncio.sleep(1) |
| | | else: |
| | | return False |
| | | |
| | | |
| | | # 批量异步创建信息,同时显示进度条 |
| | | async def batch_create_animals(batch_size: int): |
| | | tasks = [] |
| | | # 设置超时时间为60秒(可根据需要调整) |
| | | timeout = aiohttp.ClientTimeout(total=60) |
| | | async with aiohttp.ClientSession(timeout=timeout) as session: |
| | | for i in range(1, batch_size + 1): |
| | | task = asyncio.ensure_future(create_animal(session, i)) |
| | | tasks.append(task) |
| | | |
| | | success_count = 0 |
| | | # 使用 asyncio.as_completed 逐个等待任务完成,并更新进度条 |
| | | with tqdm(total=batch_size, desc="创建进度") as pbar: |
| | | for finished in asyncio.as_completed(tasks): |
| | | result = await finished |
| | | if result: |
| | | success_count += 1 |
| | | pbar.update(1) |
| | | summary = f"\n[SUMMARY] 成功创建 {success_count}/{batch_size} 个{apiname}" |
| | | print(summary) |
| | | dingtalk_helper.send_message( |
| | | f"你的批量创建{apiname}模块数据已完成:成功创建 {success_count}/{batch_size} 个{apiname},可前往系统查看!") |
| | | |
| | | |
| | | # 主入口 |
| | | if __name__ == '__main__': |
| | | n = 100000 # 可根据需要调整批量创建数量 |
| | | asyncio.run(batch_create_animals(n)) |
| New file |
| | |
| | | import asyncio |
| | | import aiohttp |
| | | import traceback |
| | | import time |
| | | from Util.random_util import RandomUtil # 你已有的工具 |
| | | from Util.dingtalk_helper import DingTalkHelper |
| | | from tqdm import tqdm |
| | | import logging |
| | | |
| | | # 钉钉机器人 access_token 和 secret |
| | | ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d' |
| | | SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19' |
| | | |
| | | apiname = "设备维保通知" |
| | | url = "http://192.168.6.168:5537/api/notify/save" |
| | | headers = { |
| | | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjA2MDczMTAsInVzZXJuYW1lIjoiZ2x5In0.3DC4-4oUMuIpSDT-dYn8bv9JvuPadIssUQfIKAnjAL4", |
| | | "Content-Type": "application/json" |
| | | } |
| | | |
| | | # 并发 worker 数(控制并发请求数),根据本机性能与被测服务能力调整,例如 50、100 |
| | | NUM_WORKERS = 100 |
| | | |
| | | # 总请求数(不要一开始就设过高) |
| | | TOTAL_REQUESTS = 2000000 |
| | | |
| | | # 每个请求最大重试次数 |
| | | MAX_RETRIES = 3 |
| | | |
| | | # aiohttp 连接/超时设置 |
| | | REQUEST_TIMEOUT = 60 # 秒 |
| | | # ------------------------ |
| | | |
| | | dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET) |
| | | |
| | | # 预生成不变的大 body 字段,减少每次构造开销 |
| | | LARGE_CONTENT = "压测中" * 500 |
| | | FILES_PATH = "/userfiles/1588133301094375425/程序附件/notify/notify/2025/10/15/173933/cs.jpg" |
| | | |
| | | |
| | | def create_animal_data(idx: int): |
| | | random_code = RandomUtil.generate_random_number_string(0, 999999999) |
| | | return { |
| | | "id": "", |
| | | "type": "6", |
| | | "title": f"压测中{random_code}", |
| | | "content": LARGE_CONTENT, |
| | | "files": FILES_PATH, |
| | | "status": "1", |
| | | "notifyRecordIds": "", |
| | | "isNotice": "0", |
| | | "receivingRange": "1" |
| | | } |
| | | |
| | | |
| | | async def create_animal(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES): |
| | | """ |
| | | 单个请求,包含重试。返回 (success: bool, index: int, errmsg: str|None) |
| | | """ |
| | | attempt = 0 |
| | | while attempt < max_retries: |
| | | data = create_animal_data(index) |
| | | try: |
| | | async with session.post(url, json=data, headers=headers) as resp: |
| | | text = await resp.text() |
| | | if resp.status == 200: |
| | | # 成功不频繁打印,交给外层进度统计 |
| | | return True, index, None |
| | | else: |
| | | # 非 200 当成失败(可自定义对特定状态的判定) |
| | | errmsg = f"状态码 {resp.status}, body: {text}" |
| | | attempt += 1 |
| | | # 指数退避,避免瞬时打爆目标 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | except Exception as e: |
| | | # 记录简短异常信息(避免每次打印超长堆栈) |
| | | err_msg = f"{type(e).__name__}: {str(e)}" |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | # 走到这里表示重试失败 |
| | | return False, index, f"重试 {max_retries} 次后失败" |
| | | |
| | | |
| | | async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, |
| | | pbar: tqdm, success_counter: dict, failed_list: list, lock: asyncio.Lock): |
| | | """ |
| | | worker 从队列获取任务并执行 create_animal |
| | | """ |
| | | while True: |
| | | idx = await queue.get() |
| | | if idx is None: |
| | | queue.task_done() |
| | | break |
| | | try: |
| | | ok, index, errmsg = await create_animal(session, idx) |
| | | async with lock: |
| | | if ok: |
| | | success_counter["count"] += 1 |
| | | else: |
| | | failed_list.append((index, errmsg)) |
| | | pbar.update(1) |
| | | except Exception as e: |
| | | # 捕获意外错误,记录并继续 |
| | | async with lock: |
| | | failed_list.append((idx, f"Worker异常: {type(e).__name__}:{e}")) |
| | | pbar.update(1) |
| | | finally: |
| | | queue.task_done() |
| | | |
| | | |
| | | async def batch_create_animals(total: int, num_workers: int): |
| | | timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT) |
| | | connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False) |
| | | # 减少session请求头每次设置的开销,也可在 post 中单独传 headers |
| | | async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session: |
| | | queue = asyncio.Queue() |
| | | for i in range(1, total + 1): |
| | | await queue.put(i) |
| | | |
| | | # 结束信号 |
| | | for _ in range(num_workers): |
| | | await queue.put(None) |
| | | |
| | | success_counter = {"count": 0} |
| | | failed_list = [] |
| | | lock = asyncio.Lock() |
| | | |
| | | # tqdm 在主线程创建 |
| | | with tqdm(total=total, desc="创建进度") as pbar: |
| | | workers = [ |
| | | asyncio.create_task(worker(i, queue, session, pbar, success_counter, failed_list, lock)) |
| | | for i in range(num_workers) |
| | | ] |
| | | # 等待所有 worker 完成 |
| | | await asyncio.gather(*workers) |
| | | |
| | | # 汇总 |
| | | total_success = success_counter["count"] |
| | | total_failed = len(failed_list) |
| | | summary = f"\n[SUMMARY] 成功创建 {total_success}/{total} 个 {apiname},失败 {total_failed} 个" |
| | | print(summary) |
| | | |
| | | # 可只发送简短信息到钉钉(避免太长) |
| | | dingtalk_helper.send_message( |
| | | f"你的批量创建{apiname}模块数据已完成:成功创建 {total_success}/{total} 个,失败 {total_failed} 个(详见日志)。" |
| | | ) |
| | | |
| | | # 如果需要打印失败详情,这里只打印前 50 条,避免控制台炸屏 |
| | | if total_failed: |
| | | print("失败示例(最多显示50条):") |
| | | for idx, err in failed_list[:50]: |
| | | print(f" #{idx} => {err}") |
| | | |
| | | |
| | | if __name__ == '__main__': |
| | | # 运行前建议: |
| | | # 1) 先用小的总数(例如 100 或 1000)跑通; |
| | | # 2) 观察目标服务器与本机负载,再把 TOTAL_REQUESTS 调高; |
| | | # 3) 调整 NUM_WORKERS 与 REQUEST_TIMEOUT 以获得最佳吞吐/稳定性。 |
| | | NUM_WORKERS = NUM_WORKERS |
| | | TOTAL_REQUESTS = TOTAL_REQUESTS |
| | | |
| | | # 简单日志设置(只打印 WARNING 及以上,避免太多日志) |
| | | logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(levelname)s %(message)s') |
| | | |
| | | asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS)) |
| New file |
| | |
| | | """ |
| | | 集成压测脚本(带压测报告生成并通过钉钉发送摘要) |
| | | |
| | | 说明: |
| | | - 该脚本基于之前的稳定 worker/队列 实现,运行后会记录每条请求的时间、状态码和延迟。 |
| | | - 运行结束后会调用Util目录下的压测报告生成器(文件名: stress_test_report_generator.py),输出 HTML/JSON/CSV/(可选)DOCX 等文件。 |
| | | - 生成后会把关键统计摘要通过 DingTalk 机器人发送(调用 DingTalkHelper.send_message)。 |
| | | - 安装依赖:aiohttp, tqdm, numpy/pandas/matplotlib/python-docx(可选) |
| | | - 确保 DingTalkHelper 类在你的 `Util.dingtalk_helper` 中可用,且 ACCESS_TOKEN/SECRET 正确。 |
| | | """ |
| | | import sys |
| | | import os |
| | | # 将上一级目录加入模块搜索路径 |
| | | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| | | import asyncio |
| | | import aiohttp |
| | | import time |
| | | import traceback |
| | | import datetime |
| | | from tqdm import tqdm |
| | | from Util.random_util import RandomUtil |
| | | from Util.dingtalk_helper import DingTalkHelper |
| | | |
| | | |
| | | # --- 配置 --- |
| | | ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d' |
| | | SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19' |
| | | |
| | | apiname = "设备维保通知" |
| | | url = "http://192.168.6.168:5537/api/notify/save" |
| | | headers = { |
| | | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjA2MDczMTAsInVzZXJuYW1lIjoiZ2x5In0.3DC4-4oUMuIpSDT-dYn8bv9JvuPadIssUQfIKAnjAL4", |
| | | "Content-Type": "application/json" |
| | | } |
| | | |
| | | NUM_WORKERS = 100 |
| | | TOTAL_REQUESTS = 2000000 |
| | | MAX_RETRIES = 3 |
| | | REQUEST_TIMEOUT = 60 |
| | | OUTPUT_DIR = './load_test_report' |
| | | |
| | | # --- 初始化 --- |
| | | dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET) |
| | | |
| | | LARGE_CONTENT = "压测中" * 500 |
| | | FILES_PATH = "/userfiles/1588133301094375425/程序附件/notify/notify/2025/10/15/173933/cs.jpg" |
| | | |
| | | |
| | | def create_animal_data(idx: int): |
| | | random_code = RandomUtil.generate_random_number_string(0, 999999999) |
| | | return { |
| | | "id": "", |
| | | "type": "6", |
| | | "title": f"压测中{random_code}", |
| | | "content": LARGE_CONTENT, |
| | | "files": FILES_PATH, |
| | | "status": "1", |
| | | "notifyRecordIds": "", |
| | | "isNotice": "0", |
| | | "receivingRange": "1" |
| | | } |
| | | |
| | | |
| | | async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES): |
| | | attempt = 0 |
| | | last_err = None |
| | | while attempt < max_retries: |
| | | data = create_animal_data(index) |
| | | start = time.time() |
| | | try: |
| | | async with session.post(url, json=data, headers=headers) as resp: |
| | | text = await resp.text() |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | status = resp.status |
| | | if status == 200: |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': status, |
| | | 'latency_ms': latency_ms, |
| | | 'response_size': len(text) if text is not None else None, |
| | | 'error': None |
| | | } |
| | | else: |
| | | last_err = f'status_{status}:{text}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | except Exception as e: |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | last_err = f'{type(e).__name__}:{str(e)}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | # 最终失败 |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': 0, |
| | | 'latency_ms': latency_ms if 'latency_ms' in locals() else 0, |
| | | 'response_size': None, |
| | | 'error': last_err |
| | | } |
| | | |
| | | |
| | | async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock): |
| | | while True: |
| | | idx = await queue.get() |
| | | if idx is None: |
| | | queue.task_done() |
| | | break |
| | | try: |
| | | res = await perform_request(session, idx) |
| | | # 记录到报告生成器 |
| | | gen.record_result( |
| | | index=res['index'], |
| | | timestamp=res['timestamp'], |
| | | status_code=int(res['status_code']), |
| | | latency_ms=float(res['latency_ms']), |
| | | response_size=res.get('response_size'), |
| | | error=res.get('error') |
| | | ) |
| | | async with lock: |
| | | if res['status_code'] and 200 <= res['status_code'] < 300: |
| | | success_counter['count'] += 1 |
| | | else: |
| | | failed_list.append((res['index'], res.get('error'))) |
| | | pbar.update(1) |
| | | except Exception as e: |
| | | async with lock: |
| | | failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}')) |
| | | pbar.update(1) |
| | | finally: |
| | | queue.task_done() |
| | | |
| | | |
| | | async def batch_create_animals(total: int, num_workers: int): |
| | | # 动态加载报告生成器模块(支持中文文件名) |
| | | gen = None |
| | | try: |
| | | import importlib.util |
| | | script_dir = os.path.dirname(os.path.abspath(__file__)) |
| | | report_path = os.path.join(script_dir, 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py') |
| | | if os.path.exists(report_path): |
| | | spec = importlib.util.spec_from_file_location('report_module', report_path) |
| | | report_module = importlib.util.module_from_spec(spec) |
| | | spec.loader.exec_module(report_module) |
| | | LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator') |
| | | else: |
| | | # 备用:尝试直接导入模块名(若你的文件名已改为 ascii) |
| | | from report_generator import LoadTestReportGenerator # type: ignore |
| | | gen = LoadTestReportGenerator(test_name='压测任务', report_title='压测详细报告') |
| | | except Exception as e: |
| | | print('无法加载压测报告生成器,请确认stress_test_report_generator.py 文件位置正确。\n', e) |
| | | raise |
| | | |
| | | timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT) |
| | | connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False) |
| | | async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session: |
| | | queue = asyncio.Queue() |
| | | for i in range(1, total + 1): |
| | | await queue.put(i) |
| | | for _ in range(num_workers): |
| | | await queue.put(None) |
| | | |
| | | success_counter = {'count': 0} |
| | | failed_list = [] |
| | | lock = asyncio.Lock() |
| | | |
| | | with tqdm(total=total, desc='创建进度') as pbar: |
| | | workers = [ |
| | | asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock)) |
| | | for i in range(num_workers) |
| | | ] |
| | | await asyncio.gather(*workers) |
| | | |
| | | # 任务完成,生成报告 |
| | | os.makedirs(OUTPUT_DIR, exist_ok=True) |
| | | outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx']) |
| | | |
| | | stats = gen.compute_stats() |
| | | |
| | | # 构造钉钉摘要消息(中文) |
| | | now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
| | | msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}'] |
| | | msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}") |
| | | msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}") |
| | | lat = stats.get('latency_ms', {}) |
| | | msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}") |
| | | |
| | | # 列出生成的报告文件 |
| | | file_list = [] |
| | | for k, v in outputs.items(): |
| | | if k == 'charts': |
| | | for cname, cpath in v.items(): |
| | | file_list.append(os.path.abspath(cpath)) |
| | | else: |
| | | file_list.append(os.path.abspath(v)) |
| | | msg.append('生成文件:') |
| | | for p in file_list: |
| | | msg.append(p) |
| | | |
| | | final_msg = '\n'.join(msg) |
| | | |
| | | # 发送钉钉消息 |
| | | try: |
| | | dingtalk_helper.send_message(final_msg) |
| | | except Exception as e: |
| | | print('发送钉钉消息失败:', e) |
| | | |
| | | print('\n[SUMMARY] 已生成报告并发送钉钉摘要。') |
| | | print('成功数:', success_counter['count'], ' 失败数:', len(failed_list)) |
| | | if failed_list: |
| | | print('失败示例(最多显示50条):') |
| | | for idx, err in failed_list[:50]: |
| | | print(f' #{idx} => {err}') |
| | | |
| | | |
| | | if __name__ == '__main__': |
| | | # 运行前建议先用小规模测试 |
| | | TOTAL_REQUESTS = TOTAL_REQUESTS |
| | | NUM_WORKERS = NUM_WORKERS |
| | | asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS)) |
| New file |
| | |
| | | |
| | | <!doctype html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="utf-8"> |
| | | <title>压测详细报告</title> |
| | | <style> |
| | | body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px} |
| | | h2{color:#2c3e50} |
| | | table{border-collapse:collapse; width:100%} |
| | | th,td{padding:6px; text-align:left} |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>压测详细报告</h1> |
| | | |
| | | <h2>摘要</h2> |
| | | <ul> |
| | | <li>报告名称:压测详细报告</li> |
| | | <li>生成时间:2025-11-12 16:49:19</li> |
| | | <li>总请求数:10000</li> |
| | | <li>成功数:10000,失败数:0,成功率:100.00%</li> |
| | | <li>总耗时(秒):493.44</li> |
| | | <li>平均吞吐(req/s):20.27</li> |
| | | </ul> |
| | | |
| | | |
| | | <h2>响应时间统计 (ms)</h2> |
| | | <ul> |
| | | <li>最小:2995.07</li> |
| | | <li>最大:8979.18</li> |
| | | <li>平均:4966.85</li> |
| | | <li>中位数(P50):4861.56</li> |
| | | <li>P90:5892.74,P95:6121.15,P99:7008.39</li> |
| | | </ul> |
| | | |
| | | <h2>状态码分布</h2><ul><li>200: 10000</li></ul> |
| | | <h2>错误汇总</h2><p>无错误记录</p> |
| | | <h2>图表</h2><div><h3>latency_hist</h3><img src="动物房管理新建动物房压测任务_20251112_164917_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="动物房管理新建动物房压测任务_20251112_164917_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div> |
| | | |
| | | <h2>请求明细(仅显示前100条)</h2> |
| | | <table border="1" cellpadding="4" cellspacing="0"> |
| | | <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr> |
| | | <tr><td>27</td><td>2025-11-12 16:41:04</td><td>200</td><td>4316.097021102905</td><td>12</td><td></td></tr><tr><td>17</td><td>2025-11-12 16:41:04</td><td>200</td><td>4503.03053855896</td><td>12</td><td></td></tr><tr><td>5</td><td>2025-11-12 16:41:04</td><td>200</td><td>4509.0272426605225</td><td>12</td><td></td></tr><tr><td>2</td><td>2025-11-12 16:41:04</td><td>200</td><td>4527.026891708374</td><td>12</td><td></td></tr><tr><td>1</td><td>2025-11-12 16:41:04</td><td>200</td><td>4531.03494644165</td><td>12</td><td></td></tr><tr><td>3</td><td>2025-11-12 16:41:04</td><td>200</td><td>4544.030666351318</td><td>12</td><td></td></tr><tr><td>11</td><td>2025-11-12 16:41:04</td><td>200</td><td>4560.0340366363525</td><td>12</td><td></td></tr><tr><td>13</td><td>2025-11-12 16:41:04</td><td>200</td><td>4577.026128768921</td><td>12</td><td></td></tr><tr><td>33</td><td>2025-11-12 16:41:04</td><td>200</td><td>4572.026252746582</td><td>12</td><td></td></tr><tr><td>4</td><td>2025-11-12 16:41:04</td><td>200</td><td>4583.027601242065</td><td>12</td><td></td></tr><tr><td>32</td><td>2025-11-12 16:41:04</td><td>200</td><td>4583.027601242065</td><td>12</td><td></td></tr><tr><td>22</td><td>2025-11-12 16:41:04</td><td>200</td><td>4594.027280807495</td><td>12</td><td></td></tr><tr><td>10</td><td>2025-11-12 16:41:04</td><td>200</td><td>4599.024772644043</td><td>12</td><td></td></tr><tr><td>21</td><td>2025-11-12 16:41:04</td><td>200</td><td>4595.026016235352</td><td>12</td><td></td></tr><tr><td>36</td><td>2025-11-12 16:41:04</td><td>200</td><td>4610.026121139526</td><td>12</td><td></td></tr><tr><td>15</td><td>2025-11-12 16:41:04</td><td>200</td><td>4618.0260181427</td><td>12</td><td></td></tr><tr><td>9</td><td>2025-11-12 16:41:04</td><td>200</td><td>4621.026039123535</td><td>12</td><td></td></tr><tr><td>14</td><td>2025-11-12 16:41:04</td><td>200</td><td>4631.024599075317</td><td>12</td><td></td></tr><tr><td>20</td><td>2025-11-12 16:41:04</td><td>200</td><td>4631.0272216796875</td><td>12</td><td></td></tr><tr><td>12</td><td>2025-11-12 16:41:04</td><td>200</td><td>4634.026288986206</td><td>12</td><td></td></tr><tr><td>49</td><td>2025-11-12 16:41:04</td><td>200</td><td>4622.025966644287</td><td>12</td><td></td></tr><tr><td>38</td><td>2025-11-12 16:41:04</td><td>200</td><td>4627.026319503784</td><td>12</td><td></td></tr><tr><td>24</td><td>2025-11-12 16:41:04</td><td>200</td><td>4633.0246925354</td><td>12</td><td></td></tr><tr><td>69</td><td>2025-11-12 16:41:04</td><td>200</td><td>4627.023458480835</td><td>12</td><td></td></tr><tr><td>19</td><td>2025-11-12 16:41:04</td><td>200</td><td>4649.024724960327</td><td>12</td><td></td></tr><tr><td>79</td><td>2025-11-12 16:41:04</td><td>200</td><td>4632.027387619019</td><td>12</td><td></td></tr><tr><td>7</td><td>2025-11-12 16:41:04</td><td>200</td><td>4659.02853012085</td><td>12</td><td></td></tr><tr><td>6</td><td>2025-11-12 16:41:04</td><td>200</td><td>4660.027503967285</td><td>12</td><td></td></tr><tr><td>47</td><td>2025-11-12 16:41:04</td><td>200</td><td>4647.025108337402</td><td>12</td><td></td></tr><tr><td>25</td><td>2025-11-12 16:41:04</td><td>200</td><td>4673.023462295532</td><td>12</td><td></td></tr><tr><td>81</td><td>2025-11-12 16:41:04</td><td>200</td><td>4654.026746749878</td><td>12</td><td></td></tr><tr><td>8</td><td>2025-11-12 16:41:04</td><td>200</td><td>4681.026220321655</td><td>12</td><td></td></tr><tr><td>18</td><td>2025-11-12 16:41:04</td><td>200</td><td>4678.02619934082</td><td>12</td><td></td></tr><tr><td>16</td><td>2025-11-12 16:41:04</td><td>200</td><td>4683.026075363159</td><td>12</td><td></td></tr><tr><td>34</td><td>2025-11-12 16:41:04</td><td>200</td><td>4680.025339126587</td><td>12</td><td></td></tr><tr><td>77</td><td>2025-11-12 16:41:04</td><td>200</td><td>4667.027473449707</td><td>12</td><td></td></tr><tr><td>63</td><td>2025-11-12 16:41:04</td><td>200</td><td>4682.023763656616</td><td>12</td><td></td></tr><tr><td>70</td><td>2025-11-12 16:41:04</td><td>200</td><td>4683.023452758789</td><td>12</td><td></td></tr><tr><td>67</td><td>2025-11-12 16:41:04</td><td>200</td><td>4687.023401260376</td><td>12</td><td></td></tr><tr><td>23</td><td>2025-11-12 16:41:04</td><td>200</td><td>4703.025102615356</td><td>12</td><td></td></tr><tr><td>92</td><td>2025-11-12 16:41:04</td><td>200</td><td>4689.024209976196</td><td>12</td><td></td></tr><tr><td>87</td><td>2025-11-12 16:41:04</td><td>200</td><td>4696.024417877197</td><td>12</td><td></td></tr><tr><td>56</td><td>2025-11-12 16:41:04</td><td>200</td><td>4708.02640914917</td><td>12</td><td></td></tr><tr><td>65</td><td>2025-11-12 16:41:04</td><td>200</td><td>4705.023765563965</td><td>12</td><td></td></tr><tr><td>68</td><td>2025-11-12 16:41:04</td><td>200</td><td>4705.024003982544</td><td>12</td><td></td></tr><tr><td>43</td><td>2025-11-12 16:41:04</td><td>200</td><td>4729.025363922119</td><td>12</td><td></td></tr><tr><td>64</td><td>2025-11-12 16:41:04</td><td>200</td><td>4725.023984909058</td><td>12</td><td></td></tr><tr><td>85</td><td>2025-11-12 16:41:04</td><td>200</td><td>4727.025270462036</td><td>12</td><td></td></tr><tr><td>58</td><td>2025-11-12 16:41:04</td><td>200</td><td>4738.027095794678</td><td>12</td><td></td></tr><tr><td>90</td><td>2025-11-12 16:41:04</td><td>200</td><td>4748.024463653564</td><td>12</td><td></td></tr><tr><td>28</td><td>2025-11-12 16:41:04</td><td>200</td><td>4771.021842956543</td><td>12</td><td></td></tr><tr><td>46</td><td>2025-11-12 16:41:04</td><td>200</td><td>4764.024019241333</td><td>12</td><td></td></tr><tr><td>26</td><td>2025-11-12 16:41:04</td><td>200</td><td>4787.028789520264</td><td>12</td><td></td></tr><tr><td>88</td><td>2025-11-12 16:41:04</td><td>200</td><td>4770.025014877319</td><td>12</td><td></td></tr><tr><td>35</td><td>2025-11-12 16:41:04</td><td>200</td><td>4790.027618408203</td><td>12</td><td></td></tr><tr><td>82</td><td>2025-11-12 16:41:04</td><td>200</td><td>4774.026393890381</td><td>12</td><td></td></tr><tr><td>50</td><td>2025-11-12 16:41:04</td><td>200</td><td>4788.024663925171</td><td>12</td><td></td></tr><tr><td>31</td><td>2025-11-12 16:41:04</td><td>200</td><td>4805.024862289429</td><td>12</td><td></td></tr><tr><td>73</td><td>2025-11-12 16:41:04</td><td>200</td><td>4792.027235031128</td><td>12</td><td></td></tr><tr><td>80</td><td>2025-11-12 16:41:04</td><td>200</td><td>4795.027494430542</td><td>12</td><td></td></tr><tr><td>94</td><td>2025-11-12 16:41:04</td><td>200</td><td>4790.027618408203</td><td>12</td><td></td></tr><tr><td>44</td><td>2025-11-12 16:41:04</td><td>200</td><td>4809.029340744019</td><td>12</td><td></td></tr><tr><td>48</td><td>2025-11-12 16:41:04</td><td>200</td><td>4815.025806427002</td><td>12</td><td></td></tr><tr><td>71</td><td>2025-11-12 16:41:04</td><td>200</td><td>4809.027433395386</td><td>12</td><td></td></tr><tr><td>72</td><td>2025-11-12 16:41:04</td><td>200</td><td>4810.025930404663</td><td>12</td><td></td></tr><tr><td>42</td><td>2025-11-12 16:41:04</td><td>200</td><td>4829.024314880371</td><td>12</td><td></td></tr><tr><td>78</td><td>2025-11-12 16:41:04</td><td>200</td><td>4818.0272579193115</td><td>12</td><td></td></tr><tr><td>57</td><td>2025-11-12 16:41:04</td><td>200</td><td>4826.025724411011</td><td>12</td><td></td></tr><tr><td>61</td><td>2025-11-12 16:41:04</td><td>200</td><td>4828.025341033936</td><td>12</td><td></td></tr><tr><td>41</td><td>2025-11-12 16:41:04</td><td>200</td><td>4837.023496627808</td><td>12</td><td></td></tr><tr><td>52</td><td>2025-11-12 16:41:04</td><td>200</td><td>4845.023393630981</td><td>12</td><td></td></tr><tr><td>29</td><td>2025-11-12 16:41:04</td><td>200</td><td>4854.026794433594</td><td>12</td><td></td></tr><tr><td>30</td><td>2025-11-12 16:41:04</td><td>200</td><td>4857.025146484375</td><td>12</td><td></td></tr><tr><td>84</td><td>2025-11-12 16:41:04</td><td>200</td><td>4839.025497436523</td><td>12</td><td></td></tr><tr><td>37</td><td>2025-11-12 16:41:04</td><td>200</td><td>4856.026649475098</td><td>12</td><td></td></tr><tr><td>59</td><td>2025-11-12 16:41:04</td><td>200</td><td>4863.027334213257</td><td>12</td><td></td></tr><tr><td>60</td><td>2025-11-12 16:41:04</td><td>200</td><td>4864.0265464782715</td><td>12</td><td></td></tr><tr><td>93</td><td>2025-11-12 16:41:04</td><td>200</td><td>4853.025436401367</td><td>12</td><td></td></tr><tr><td>54</td><td>2025-11-12 16:41:04</td><td>200</td><td>4870.025157928467</td><td>12</td><td></td></tr><tr><td>75</td><td>2025-11-12 16:41:04</td><td>200</td><td>4864.025831222534</td><td>12</td><td></td></tr><tr><td>89</td><td>2025-11-12 16:41:04</td><td>200</td><td>4860.024690628052</td><td>12</td><td></td></tr><tr><td>97</td><td>2025-11-12 16:41:04</td><td>200</td><td>4859.026193618774</td><td>12</td><td></td></tr><tr><td>86</td><td>2025-11-12 16:41:05</td><td>200</td><td>4870.026350021362</td><td>12</td><td></td></tr><tr><td>96</td><td>2025-11-12 16:41:05</td><td>200</td><td>4868.025541305542</td><td>12</td><td></td></tr><tr><td>53</td><td>2025-11-12 16:41:05</td><td>200</td><td>4886.023998260498</td><td>12</td><td></td></tr><tr><td>98</td><td>2025-11-12 16:41:05</td><td>200</td><td>4877.027273178101</td><td>12</td><td></td></tr><tr><td>51</td><td>2025-11-12 16:41:05</td><td>200</td><td>4895.024061203003</td><td>12</td><td></td></tr><tr><td>74</td><td>2025-11-12 16:41:05</td><td>200</td><td>4888.025999069214</td><td>12</td><td></td></tr><tr><td>100</td><td>2025-11-12 16:41:05</td><td>200</td><td>4882.0250034332275</td><td>12</td><td></td></tr><tr><td>99</td><td>2025-11-12 16:41:05</td><td>200</td><td>4882.0250034332275</td><td>12</td><td></td></tr><tr><td>39</td><td>2025-11-12 16:41:05</td><td>200</td><td>4905.02667427063</td><td>12</td><td></td></tr><tr><td>55</td><td>2025-11-12 16:41:05</td><td>200</td><td>4910.027503967285</td><td>12</td><td></td></tr><tr><td>91</td><td>2025-11-12 16:41:05</td><td>200</td><td>4905.026197433472</td><td>12</td><td></td></tr><tr><td>62</td><td>2025-11-12 16:41:05</td><td>200</td><td>4927.027463912964</td><td>12</td><td></td></tr><tr><td>83</td><td>2025-11-12 16:41:05</td><td>200</td><td>4921.027183532715</td><td>12</td><td></td></tr><tr><td>76</td><td>2025-11-12 16:41:05</td><td>200</td><td>4948.025703430176</td><td>12</td><td></td></tr><tr><td>45</td><td>2025-11-12 16:41:05</td><td>200</td><td>4999.026536941528</td><td>12</td><td></td></tr><tr><td>95</td><td>2025-11-12 16:41:05</td><td>200</td><td>5127.027750015259</td><td>12</td><td></td></tr><tr><td>66</td><td>2025-11-12 16:41:05</td><td>200</td><td>5412.693023681641</td><td>12</td><td></td></tr><tr><td>40</td><td>2025-11-12 16:41:05</td><td>200</td><td>5575.217962265015</td><td>12</td><td></td></tr> |
| | | </table> |
| | | |
| | | <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p> |
| | | </body> |
| | | </html> |
| | | |
| New file |
| | |
| | | |
| | | <!doctype html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="utf-8"> |
| | | <title>压测详细报告</title> |
| | | <style> |
| | | body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px} |
| | | h2{color:#2c3e50} |
| | | table{border-collapse:collapse; width:100%} |
| | | th,td{padding:6px; text-align:left} |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>压测详细报告</h1> |
| | | |
| | | <h2>摘要</h2> |
| | | <ul> |
| | | <li>报告名称:压测详细报告</li> |
| | | <li>生成时间:2025-11-12 17:50:53</li> |
| | | <li>总请求数:10000</li> |
| | | <li>成功数:10000,失败数:0,成功率:100.00%</li> |
| | | <li>总耗时(秒):525.46</li> |
| | | <li>平均吞吐(req/s):19.03</li> |
| | | </ul> |
| | | |
| | | |
| | | <h2>响应时间统计 (ms)</h2> |
| | | <ul> |
| | | <li>最小:2581.28</li> |
| | | <li>最大:11187.67</li> |
| | | <li>平均:5282.15</li> |
| | | <li>中位数(P50):5057.26</li> |
| | | <li>P90:6251.82,P95:6611.83,P99:8029.87</li> |
| | | </ul> |
| | | |
| | | <h2>状态码分布</h2><ul><li>200: 10000</li></ul> |
| | | <h2>错误汇总</h2><p>无错误记录</p> |
| | | <h2>图表</h2><div><h3>latency_hist</h3><img src="动物房管理新建动物房压测任务_20251112_175052_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="动物房管理新建动物房压测任务_20251112_175052_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div> |
| | | |
| | | <h2>请求明细(仅显示前100条)</h2> |
| | | <table border="1" cellpadding="4" cellspacing="0"> |
| | | <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr> |
| | | <tr><td>8</td><td>2025-11-12 17:42:06</td><td>200</td><td>4016.1163806915283</td><td>12</td><td></td></tr><tr><td>11</td><td>2025-11-12 17:42:06</td><td>200</td><td>4239.122629165649</td><td>12</td><td></td></tr><tr><td>6</td><td>2025-11-12 17:42:06</td><td>200</td><td>4252.122640609741</td><td>12</td><td></td></tr><tr><td>2</td><td>2025-11-12 17:42:06</td><td>200</td><td>4258.126497268677</td><td>12</td><td></td></tr><tr><td>9</td><td>2025-11-12 17:42:06</td><td>200</td><td>4266.122341156006</td><td>12</td><td></td></tr><tr><td>10</td><td>2025-11-12 17:42:06</td><td>200</td><td>4271.126985549927</td><td>12</td><td></td></tr><tr><td>7</td><td>2025-11-12 17:42:06</td><td>200</td><td>4277.122020721436</td><td>12</td><td></td></tr><tr><td>1</td><td>2025-11-12 17:42:06</td><td>200</td><td>4286.126136779785</td><td>12</td><td></td></tr><tr><td>4</td><td>2025-11-12 17:42:06</td><td>200</td><td>4299.125909805298</td><td>12</td><td></td></tr><tr><td>30</td><td>2025-11-12 17:42:06</td><td>200</td><td>4308.122396469116</td><td>12</td><td></td></tr><tr><td>5</td><td>2025-11-12 17:42:06</td><td>200</td><td>4318.123817443848</td><td>12</td><td></td></tr><tr><td>33</td><td>2025-11-12 17:42:06</td><td>200</td><td>4313.12108039856</td><td>12</td><td></td></tr><tr><td>13</td><td>2025-11-12 17:42:06</td><td>200</td><td>4326.1213302612305</td><td>12</td><td></td></tr><tr><td>12</td><td>2025-11-12 17:42:06</td><td>200</td><td>4332.123517990112</td><td>12</td><td></td></tr><tr><td>31</td><td>2025-11-12 17:42:06</td><td>200</td><td>4332.1213722229</td><td>12</td><td></td></tr><tr><td>34</td><td>2025-11-12 17:42:06</td><td>200</td><td>4332.123041152954</td><td>12</td><td></td></tr><tr><td>15</td><td>2025-11-12 17:42:06</td><td>200</td><td>4342.123031616211</td><td>12</td><td></td></tr><tr><td>24</td><td>2025-11-12 17:42:06</td><td>200</td><td>4340.123414993286</td><td>12</td><td></td></tr><tr><td>21</td><td>2025-11-12 17:42:06</td><td>200</td><td>4356.121778488159</td><td>12</td><td></td></tr><tr><td>26</td><td>2025-11-12 17:42:06</td><td>200</td><td>4355.121612548828</td><td>12</td><td></td></tr><tr><td>32</td><td>2025-11-12 17:42:06</td><td>200</td><td>4354.120969772339</td><td>12</td><td></td></tr><tr><td>17</td><td>2025-11-12 17:42:06</td><td>200</td><td>4361.12117767334</td><td>12</td><td></td></tr><tr><td>27</td><td>2025-11-12 17:42:06</td><td>200</td><td>4360.122919082642</td><td>12</td><td></td></tr><tr><td>19</td><td>2025-11-12 17:42:06</td><td>200</td><td>4369.12202835083</td><td>12</td><td></td></tr><tr><td>14</td><td>2025-11-12 17:42:06</td><td>200</td><td>4378.149032592773</td><td>12</td><td></td></tr><tr><td>16</td><td>2025-11-12 17:42:06</td><td>200</td><td>4383.121728897095</td><td>12</td><td></td></tr><tr><td>29</td><td>2025-11-12 17:42:06</td><td>200</td><td>4384.122133255005</td><td>12</td><td></td></tr><tr><td>22</td><td>2025-11-12 17:42:06</td><td>200</td><td>4388.124227523804</td><td>12</td><td></td></tr><tr><td>25</td><td>2025-11-12 17:42:06</td><td>200</td><td>4389.121770858765</td><td>12</td><td></td></tr><tr><td>56</td><td>2025-11-12 17:42:06</td><td>200</td><td>4406.123161315918</td><td>12</td><td></td></tr><tr><td>47</td><td>2025-11-12 17:42:06</td><td>200</td><td>4410.123348236084</td><td>12</td><td></td></tr><tr><td>40</td><td>2025-11-12 17:42:06</td><td>200</td><td>4413.124084472656</td><td>12</td><td></td></tr><tr><td>63</td><td>2025-11-12 17:42:06</td><td>200</td><td>4406.125783920288</td><td>12</td><td></td></tr><tr><td>51</td><td>2025-11-12 17:42:06</td><td>200</td><td>4410.128593444824</td><td>12</td><td></td></tr><tr><td>3</td><td>2025-11-12 17:42:06</td><td>200</td><td>4439.1210079193115</td><td>12</td><td></td></tr><tr><td>37</td><td>2025-11-12 17:42:06</td><td>200</td><td>4427.123308181763</td><td>12</td><td></td></tr><tr><td>20</td><td>2025-11-12 17:42:06</td><td>200</td><td>4436.121940612793</td><td>12</td><td></td></tr><tr><td>89</td><td>2025-11-12 17:42:06</td><td>200</td><td>4416.125774383545</td><td>12</td><td></td></tr><tr><td>64</td><td>2025-11-12 17:42:06</td><td>200</td><td>4426.120758056641</td><td>12</td><td></td></tr><tr><td>65</td><td>2025-11-12 17:42:06</td><td>200</td><td>4426.12361907959</td><td>12</td><td></td></tr><tr><td>28</td><td>2025-11-12 17:42:06</td><td>200</td><td>4445.1234340667725</td><td>12</td><td></td></tr><tr><td>59</td><td>2025-11-12 17:42:06</td><td>200</td><td>4435.127735137939</td><td>12</td><td></td></tr><tr><td>38</td><td>2025-11-12 17:42:06</td><td>200</td><td>4445.130348205566</td><td>12</td><td></td></tr><tr><td>81</td><td>2025-11-12 17:42:06</td><td>200</td><td>4440.12451171875</td><td>12</td><td></td></tr><tr><td>100</td><td>2025-11-12 17:42:06</td><td>200</td><td>4434.125185012817</td><td>12</td><td></td></tr><tr><td>43</td><td>2025-11-12 17:42:06</td><td>200</td><td>4456.123113632202</td><td>12</td><td></td></tr><tr><td>73</td><td>2025-11-12 17:42:06</td><td>200</td><td>4446.1236000061035</td><td>12</td><td></td></tr><tr><td>61</td><td>2025-11-12 17:42:06</td><td>200</td><td>4452.127695083618</td><td>12</td><td></td></tr><tr><td>54</td><td>2025-11-12 17:42:06</td><td>200</td><td>4455.12580871582</td><td>12</td><td></td></tr><tr><td>71</td><td>2025-11-12 17:42:06</td><td>200</td><td>4450.122833251953</td><td>12</td><td></td></tr><tr><td>74</td><td>2025-11-12 17:42:06</td><td>200</td><td>4458.124399185181</td><td>12</td><td></td></tr><tr><td>50</td><td>2025-11-12 17:42:06</td><td>200</td><td>4468.123197555542</td><td>12</td><td></td></tr><tr><td>69</td><td>2025-11-12 17:42:06</td><td>200</td><td>4462.125301361084</td><td>12</td><td></td></tr><tr><td>58</td><td>2025-11-12 17:42:06</td><td>200</td><td>4467.124700546265</td><td>12</td><td></td></tr><tr><td>49</td><td>2025-11-12 17:42:06</td><td>200</td><td>4473.12068939209</td><td>12</td><td></td></tr><tr><td>87</td><td>2025-11-12 17:42:06</td><td>200</td><td>4460.125207901001</td><td>12</td><td></td></tr><tr><td>53</td><td>2025-11-12 17:42:06</td><td>200</td><td>4481.122970581055</td><td>12</td><td></td></tr><tr><td>41</td><td>2025-11-12 17:42:06</td><td>200</td><td>4486.126184463501</td><td>12</td><td></td></tr><tr><td>48</td><td>2025-11-12 17:42:06</td><td>200</td><td>4486.12117767334</td><td>12</td><td></td></tr><tr><td>46</td><td>2025-11-12 17:42:06</td><td>200</td><td>4491.122245788574</td><td>12</td><td></td></tr><tr><td>52</td><td>2025-11-12 17:42:06</td><td>200</td><td>4494.122743606567</td><td>12</td><td></td></tr><tr><td>60</td><td>2025-11-12 17:42:06</td><td>200</td><td>4492.126941680908</td><td>12</td><td></td></tr><tr><td>57</td><td>2025-11-12 17:42:06</td><td>200</td><td>4494.1229820251465</td><td>12</td><td></td></tr><tr><td>55</td><td>2025-11-12 17:42:06</td><td>200</td><td>4496.124744415283</td><td>12</td><td></td></tr><tr><td>42</td><td>2025-11-12 17:42:06</td><td>200</td><td>4509.125709533691</td><td>12</td><td></td></tr><tr><td>92</td><td>2025-11-12 17:42:07</td><td>200</td><td>4492.124795913696</td><td>12</td><td></td></tr><tr><td>95</td><td>2025-11-12 17:42:07</td><td>200</td><td>4493.124961853027</td><td>12</td><td></td></tr><tr><td>39</td><td>2025-11-12 17:42:07</td><td>200</td><td>4516.12401008606</td><td>12</td><td></td></tr><tr><td>98</td><td>2025-11-12 17:42:07</td><td>200</td><td>4495.122909545898</td><td>12</td><td></td></tr><tr><td>86</td><td>2025-11-12 17:42:07</td><td>200</td><td>4503.12352180481</td><td>12</td><td></td></tr><tr><td>84</td><td>2025-11-12 17:42:07</td><td>200</td><td>4504.121541976929</td><td>12</td><td></td></tr><tr><td>78</td><td>2025-11-12 17:42:07</td><td>200</td><td>4513.124465942383</td><td>12</td><td></td></tr><tr><td>62</td><td>2025-11-12 17:42:07</td><td>200</td><td>4521.124839782715</td><td>12</td><td></td></tr><tr><td>80</td><td>2025-11-12 17:42:07</td><td>200</td><td>4516.124248504639</td><td>12</td><td></td></tr><tr><td>76</td><td>2025-11-12 17:42:07</td><td>200</td><td>4517.125606536865</td><td>12</td><td></td></tr><tr><td>44</td><td>2025-11-12 17:42:07</td><td>200</td><td>4532.1221351623535</td><td>12</td><td></td></tr><tr><td>91</td><td>2025-11-12 17:42:07</td><td>200</td><td>4523.122787475586</td><td>12</td><td></td></tr><tr><td>82</td><td>2025-11-12 17:42:07</td><td>200</td><td>4527.12345123291</td><td>12</td><td></td></tr><tr><td>68</td><td>2025-11-12 17:42:07</td><td>200</td><td>4535.124063491821</td><td>12</td><td></td></tr><tr><td>77</td><td>2025-11-12 17:42:07</td><td>200</td><td>4533.736705780029</td><td>12</td><td></td></tr><tr><td>75</td><td>2025-11-12 17:42:07</td><td>200</td><td>4536.733150482178</td><td>12</td><td></td></tr><tr><td>85</td><td>2025-11-12 17:42:07</td><td>200</td><td>4539.735317230225</td><td>12</td><td></td></tr><tr><td>36</td><td>2025-11-12 17:42:07</td><td>200</td><td>4562.7336502075195</td><td>12</td><td></td></tr><tr><td>83</td><td>2025-11-12 17:42:07</td><td>200</td><td>4546.7376708984375</td><td>12</td><td></td></tr><tr><td>93</td><td>2025-11-12 17:42:07</td><td>200</td><td>4543.737173080444</td><td>12</td><td></td></tr><tr><td>45</td><td>2025-11-12 17:42:07</td><td>200</td><td>4566.736698150635</td><td>12</td><td></td></tr><tr><td>35</td><td>2025-11-12 17:42:07</td><td>200</td><td>4573.734283447266</td><td>12</td><td></td></tr><tr><td>94</td><td>2025-11-12 17:42:07</td><td>200</td><td>4557.732820510864</td><td>12</td><td></td></tr><tr><td>67</td><td>2025-11-12 17:42:07</td><td>200</td><td>4570.733308792114</td><td>12</td><td></td></tr><tr><td>90</td><td>2025-11-12 17:42:07</td><td>200</td><td>4564.733028411865</td><td>12</td><td></td></tr><tr><td>66</td><td>2025-11-12 17:42:07</td><td>200</td><td>4574.735403060913</td><td>12</td><td></td></tr><tr><td>79</td><td>2025-11-12 17:42:07</td><td>200</td><td>4577.73232460022</td><td>12</td><td></td></tr><tr><td>96</td><td>2025-11-12 17:42:07</td><td>200</td><td>4572.735786437988</td><td>12</td><td></td></tr><tr><td>97</td><td>2025-11-12 17:42:07</td><td>200</td><td>4581.734418869019</td><td>12</td><td></td></tr><tr><td>70</td><td>2025-11-12 17:42:07</td><td>200</td><td>4596.734285354614</td><td>12</td><td></td></tr><tr><td>99</td><td>2025-11-12 17:42:07</td><td>200</td><td>4652.73118019104</td><td>12</td><td></td></tr><tr><td>88</td><td>2025-11-12 17:42:07</td><td>200</td><td>4658.735513687134</td><td>12</td><td></td></tr><tr><td>23</td><td>2025-11-12 17:42:07</td><td>200</td><td>4700.731992721558</td><td>12</td><td></td></tr><tr><td>18</td><td>2025-11-12 17:42:07</td><td>200</td><td>4704.733848571777</td><td>12</td><td></td></tr><tr><td>72</td><td>2025-11-12 17:42:07</td><td>200</td><td>4690.733194351196</td><td>12</td><td></td></tr> |
| | | </table> |
| | | |
| | | <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p> |
| | | </body> |
| | | </html> |
| | | |
| New file |
| | |
| | | |
| | | <!doctype html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="utf-8"> |
| | | <title>压测详细报告</title> |
| | | <style> |
| | | body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px} |
| | | h2{color:#2c3e50} |
| | | table{border-collapse:collapse; width:100%} |
| | | th,td{padding:6px; text-align:left} |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>压测详细报告</h1> |
| | | |
| | | <h2>摘要</h2> |
| | | <ul> |
| | | <li>报告名称:压测详细报告</li> |
| | | <li>生成时间:2025-11-14 11:10:51</li> |
| | | <li>总请求数:10000</li> |
| | | <li>成功数:10000,失败数:0,成功率:100.00%</li> |
| | | <li>总耗时(秒):492.49</li> |
| | | <li>平均吞吐(req/s):20.31</li> |
| | | </ul> |
| | | |
| | | |
| | | <h2>响应时间统计 (ms)</h2> |
| | | <ul> |
| | | <li>最小:3003.90</li> |
| | | <li>最大:9071.00</li> |
| | | <li>平均:4959.57</li> |
| | | <li>中位数(P50):4861.27</li> |
| | | <li>P90:5760.03,P95:6109.71,P99:6898.13</li> |
| | | </ul> |
| | | |
| | | <h2>状态码分布</h2><ul><li>200: 10000</li></ul> |
| | | <h2>错误汇总</h2><p>无错误记录</p> |
| | | <h2>图表</h2><div><h3>latency_hist</h3><img src="动物房管理新建动物房压测任务_20251114_111050_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="动物房管理新建动物房压测任务_20251114_111050_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div> |
| | | |
| | | <h2>请求明细(仅显示前100条)</h2> |
| | | <table border="1" cellpadding="4" cellspacing="0"> |
| | | <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr> |
| | | <tr><td>53</td><td>2025-11-14 11:02:37</td><td>200</td><td>4442.166328430176</td><td>12</td><td></td></tr><tr><td>6</td><td>2025-11-14 11:02:37</td><td>200</td><td>4672.16682434082</td><td>12</td><td></td></tr><tr><td>1</td><td>2025-11-14 11:02:37</td><td>200</td><td>4678.151845932007</td><td>12</td><td></td></tr><tr><td>11</td><td>2025-11-14 11:02:37</td><td>200</td><td>4693.154811859131</td><td>12</td><td></td></tr><tr><td>2</td><td>2025-11-14 11:02:37</td><td>200</td><td>4707.14259147644</td><td>12</td><td></td></tr><tr><td>7</td><td>2025-11-14 11:02:37</td><td>200</td><td>4706.165790557861</td><td>12</td><td></td></tr><tr><td>5</td><td>2025-11-14 11:02:37</td><td>200</td><td>4709.152698516846</td><td>12</td><td></td></tr><tr><td>9</td><td>2025-11-14 11:02:37</td><td>200</td><td>4711.154937744141</td><td>12</td><td></td></tr><tr><td>4</td><td>2025-11-14 11:02:37</td><td>200</td><td>4731.152296066284</td><td>12</td><td></td></tr><tr><td>16</td><td>2025-11-14 11:02:37</td><td>200</td><td>4726.15647315979</td><td>12</td><td></td></tr><tr><td>3</td><td>2025-11-14 11:02:37</td><td>200</td><td>4736.144065856934</td><td>12</td><td></td></tr><tr><td>22</td><td>2025-11-14 11:02:37</td><td>200</td><td>4729.156970977783</td><td>12</td><td></td></tr><tr><td>15</td><td>2025-11-14 11:02:37</td><td>200</td><td>4750.155210494995</td><td>12</td><td></td></tr><tr><td>10</td><td>2025-11-14 11:02:37</td><td>200</td><td>4766.148805618286</td><td>12</td><td></td></tr><tr><td>23</td><td>2025-11-14 11:02:37</td><td>200</td><td>4761.157274246216</td><td>12</td><td></td></tr><tr><td>17</td><td>2025-11-14 11:02:37</td><td>200</td><td>4763.1566524505615</td><td>12</td><td></td></tr><tr><td>18</td><td>2025-11-14 11:02:37</td><td>200</td><td>4765.156030654907</td><td>12</td><td></td></tr><tr><td>29</td><td>2025-11-14 11:02:37</td><td>200</td><td>4765.155792236328</td><td>12</td><td></td></tr><tr><td>24</td><td>2025-11-14 11:02:37</td><td>200</td><td>4787.159204483032</td><td>12</td><td></td></tr><tr><td>8</td><td>2025-11-14 11:02:37</td><td>200</td><td>4798.150062561035</td><td>12</td><td></td></tr><tr><td>21</td><td>2025-11-14 11:02:37</td><td>200</td><td>4790.1575565338135</td><td>12</td><td></td></tr><tr><td>27</td><td>2025-11-14 11:02:37</td><td>200</td><td>4789.155721664429</td><td>12</td><td></td></tr><tr><td>20</td><td>2025-11-14 11:02:37</td><td>200</td><td>4792.155981063843</td><td>12</td><td></td></tr><tr><td>13</td><td>2025-11-14 11:02:37</td><td>200</td><td>4796.151638031006</td><td>12</td><td></td></tr><tr><td>42</td><td>2025-11-14 11:02:37</td><td>200</td><td>4800.156116485596</td><td>12</td><td></td></tr><tr><td>19</td><td>2025-11-14 11:02:37</td><td>200</td><td>4809.157133102417</td><td>12</td><td></td></tr><tr><td>44</td><td>2025-11-14 11:02:37</td><td>200</td><td>4801.157236099243</td><td>12</td><td></td></tr><tr><td>39</td><td>2025-11-14 11:02:37</td><td>200</td><td>4804.156064987183</td><td>12</td><td></td></tr><tr><td>50</td><td>2025-11-14 11:02:37</td><td>200</td><td>4801.158905029297</td><td>12</td><td></td></tr><tr><td>14</td><td>2025-11-14 11:02:37</td><td>200</td><td>4829.150438308716</td><td>12</td><td></td></tr><tr><td>64</td><td>2025-11-14 11:02:37</td><td>200</td><td>4812.152624130249</td><td>12</td><td></td></tr><tr><td>48</td><td>2025-11-14 11:02:37</td><td>200</td><td>4819.155693054199</td><td>12</td><td></td></tr><tr><td>30</td><td>2025-11-14 11:02:37</td><td>200</td><td>4825.156927108765</td><td>12</td><td></td></tr><tr><td>37</td><td>2025-11-14 11:02:37</td><td>200</td><td>4824.157476425171</td><td>12</td><td></td></tr><tr><td>12</td><td>2025-11-14 11:02:37</td><td>200</td><td>4837.149620056152</td><td>12</td><td></td></tr><tr><td>31</td><td>2025-11-14 11:02:37</td><td>200</td><td>4842.155933380127</td><td>12</td><td></td></tr><tr><td>45</td><td>2025-11-14 11:02:37</td><td>200</td><td>4838.156461715698</td><td>12</td><td></td></tr><tr><td>33</td><td>2025-11-14 11:02:37</td><td>200</td><td>4843.156099319458</td><td>12</td><td></td></tr><tr><td>40</td><td>2025-11-14 11:02:37</td><td>200</td><td>4842.155933380127</td><td>12</td><td></td></tr><tr><td>57</td><td>2025-11-14 11:02:37</td><td>200</td><td>4842.156887054443</td><td>12</td><td></td></tr><tr><td>25</td><td>2025-11-14 11:02:37</td><td>200</td><td>4854.153871536255</td><td>12</td><td></td></tr><tr><td>47</td><td>2025-11-14 11:02:37</td><td>200</td><td>4848.155498504639</td><td>12</td><td></td></tr><tr><td>26</td><td>2025-11-14 11:02:37</td><td>200</td><td>4864.153623580933</td><td>12</td><td></td></tr><tr><td>56</td><td>2025-11-14 11:02:37</td><td>200</td><td>4854.156255722046</td><td>12</td><td></td></tr><tr><td>41</td><td>2025-11-14 11:02:37</td><td>200</td><td>4869.156360626221</td><td>12</td><td></td></tr><tr><td>38</td><td>2025-11-14 11:02:37</td><td>200</td><td>4871.1559772491455</td><td>12</td><td></td></tr><tr><td>61</td><td>2025-11-14 11:02:37</td><td>200</td><td>4866.154193878174</td><td>12</td><td></td></tr><tr><td>36</td><td>2025-11-14 11:02:37</td><td>200</td><td>4881.155967712402</td><td>12</td><td></td></tr><tr><td>62</td><td>2025-11-14 11:02:37</td><td>200</td><td>4873.154640197754</td><td>12</td><td></td></tr><tr><td>46</td><td>2025-11-14 11:02:37</td><td>200</td><td>4881.155967712402</td><td>12</td><td></td></tr><tr><td>59</td><td>2025-11-14 11:02:37</td><td>200</td><td>4882.156133651733</td><td>12</td><td></td></tr><tr><td>34</td><td>2025-11-14 11:02:37</td><td>200</td><td>4892.156600952148</td><td>12</td><td></td></tr><tr><td>63</td><td>2025-11-14 11:02:37</td><td>200</td><td>4884.154558181763</td><td>12</td><td></td></tr><tr><td>76</td><td>2025-11-14 11:02:37</td><td>200</td><td>4883.150815963745</td><td>12</td><td></td></tr><tr><td>75</td><td>2025-11-14 11:02:37</td><td>200</td><td>4884.151935577393</td><td>12</td><td></td></tr><tr><td>80</td><td>2025-11-14 11:02:37</td><td>200</td><td>4885.152578353882</td><td>12</td><td></td></tr><tr><td>90</td><td>2025-11-14 11:02:37</td><td>200</td><td>4889.156341552734</td><td>12</td><td></td></tr><tr><td>43</td><td>2025-11-14 11:02:37</td><td>200</td><td>4907.156229019165</td><td>12</td><td></td></tr><tr><td>58</td><td>2025-11-14 11:02:37</td><td>200</td><td>4902.156352996826</td><td>12</td><td></td></tr><tr><td>52</td><td>2025-11-14 11:02:37</td><td>200</td><td>4911.156177520752</td><td>12</td><td></td></tr><tr><td>32</td><td>2025-11-14 11:02:37</td><td>200</td><td>4919.156074523926</td><td>12</td><td></td></tr><tr><td>69</td><td>2025-11-14 11:02:37</td><td>200</td><td>4906.151294708252</td><td>12</td><td></td></tr><tr><td>71</td><td>2025-11-14 11:02:37</td><td>200</td><td>4911.150217056274</td><td>12</td><td></td></tr><tr><td>97</td><td>2025-11-14 11:02:37</td><td>200</td><td>4905.157566070557</td><td>12</td><td></td></tr><tr><td>74</td><td>2025-11-14 11:02:38</td><td>200</td><td>4926.154136657715</td><td>12</td><td></td></tr><tr><td>96</td><td>2025-11-14 11:02:38</td><td>200</td><td>4919.156074523926</td><td>12</td><td></td></tr><tr><td>66</td><td>2025-11-14 11:02:38</td><td>200</td><td>4931.15234375</td><td>12</td><td></td></tr><tr><td>51</td><td>2025-11-14 11:02:38</td><td>200</td><td>4938.157558441162</td><td>12</td><td></td></tr><tr><td>55</td><td>2025-11-14 11:02:38</td><td>200</td><td>4937.156438827515</td><td>12</td><td></td></tr><tr><td>83</td><td>2025-11-14 11:02:38</td><td>200</td><td>4928.1580448150635</td><td>12</td><td></td></tr><tr><td>60</td><td>2025-11-14 11:02:38</td><td>200</td><td>4938.157081604004</td><td>12</td><td></td></tr><tr><td>35</td><td>2025-11-14 11:02:38</td><td>200</td><td>4959.1639041900635</td><td>12</td><td></td></tr><tr><td>100</td><td>2025-11-14 11:02:38</td><td>200</td><td>4938.153982162476</td><td>12</td><td></td></tr><tr><td>68</td><td>2025-11-14 11:02:38</td><td>200</td><td>4952.157497406006</td><td>12</td><td></td></tr><tr><td>87</td><td>2025-11-14 11:02:38</td><td>200</td><td>4947.15690612793</td><td>12</td><td></td></tr><tr><td>70</td><td>2025-11-14 11:02:38</td><td>200</td><td>4955.159902572632</td><td>12</td><td></td></tr><tr><td>85</td><td>2025-11-14 11:02:38</td><td>200</td><td>4951.161623001099</td><td>12</td><td></td></tr><tr><td>65</td><td>2025-11-14 11:02:38</td><td>200</td><td>4970.165491104126</td><td>12</td><td></td></tr><tr><td>54</td><td>2025-11-14 11:02:38</td><td>200</td><td>4976.1693477630615</td><td>12</td><td></td></tr><tr><td>98</td><td>2025-11-14 11:02:38</td><td>200</td><td>4961.169481277466</td><td>12</td><td></td></tr><tr><td>82</td><td>2025-11-14 11:02:38</td><td>200</td><td>4969.165563583374</td><td>12</td><td></td></tr><tr><td>73</td><td>2025-11-14 11:02:38</td><td>200</td><td>4973.166704177856</td><td>12</td><td></td></tr><tr><td>94</td><td>2025-11-14 11:02:38</td><td>200</td><td>4966.169357299805</td><td>12</td><td></td></tr><tr><td>92</td><td>2025-11-14 11:02:38</td><td>200</td><td>4981.168270111084</td><td>12</td><td></td></tr><tr><td>89</td><td>2025-11-14 11:02:38</td><td>200</td><td>4983.169317245483</td><td>12</td><td></td></tr><tr><td>91</td><td>2025-11-14 11:02:38</td><td>200</td><td>4984.169960021973</td><td>12</td><td></td></tr><tr><td>72</td><td>2025-11-14 11:02:38</td><td>200</td><td>4991.16849899292</td><td>12</td><td></td></tr><tr><td>77</td><td>2025-11-14 11:02:38</td><td>200</td><td>4990.165710449219</td><td>12</td><td></td></tr><tr><td>79</td><td>2025-11-14 11:02:38</td><td>200</td><td>4990.162372589111</td><td>12</td><td></td></tr><tr><td>67</td><td>2025-11-14 11:02:38</td><td>200</td><td>4996.165037155151</td><td>12</td><td></td></tr><tr><td>49</td><td>2025-11-14 11:02:38</td><td>200</td><td>5012.169599533081</td><td>12</td><td></td></tr><tr><td>88</td><td>2025-11-14 11:02:38</td><td>200</td><td>5001.169204711914</td><td>12</td><td></td></tr><tr><td>81</td><td>2025-11-14 11:02:38</td><td>200</td><td>5004.1663646698</td><td>12</td><td></td></tr><tr><td>78</td><td>2025-11-14 11:02:38</td><td>200</td><td>5010.161876678467</td><td>12</td><td></td></tr><tr><td>95</td><td>2025-11-14 11:02:38</td><td>200</td><td>5006.16979598999</td><td>12</td><td></td></tr><tr><td>86</td><td>2025-11-14 11:02:38</td><td>200</td><td>5021.169424057007</td><td>12</td><td></td></tr><tr><td>93</td><td>2025-11-14 11:02:38</td><td>200</td><td>5041.167974472046</td><td>12</td><td></td></tr><tr><td>84</td><td>2025-11-14 11:02:38</td><td>200</td><td>5073.168992996216</td><td>12</td><td></td></tr><tr><td>99</td><td>2025-11-14 11:02:38</td><td>200</td><td>5189.169406890869</td><td>12</td><td></td></tr><tr><td>28</td><td>2025-11-14 11:02:38</td><td>200</td><td>5306.169033050537</td><td>12</td><td></td></tr> |
| | | </table> |
| | | |
| | | <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p> |
| | | </body> |
| | | </html> |
| | | |
| New file |
| | |
| | | |
| | | <!doctype html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="utf-8"> |
| | | <title>压测详细报告</title> |
| | | <style> |
| | | body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px} |
| | | h2{color:#2c3e50} |
| | | table{border-collapse:collapse; width:100%} |
| | | th,td{padding:6px; text-align:left} |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>压测详细报告</h1> |
| | | |
| | | <h2>摘要</h2> |
| | | <ul> |
| | | <li>报告名称:压测详细报告</li> |
| | | <li>生成时间:2025-11-14 11:20:10</li> |
| | | <li>总请求数:10000</li> |
| | | <li>成功数:10000,失败数:0,成功率:100.00%</li> |
| | | <li>总耗时(秒):521.91</li> |
| | | <li>平均吞吐(req/s):19.16</li> |
| | | </ul> |
| | | |
| | | |
| | | <h2>响应时间统计 (ms)</h2> |
| | | <ul> |
| | | <li>最小:2943.98</li> |
| | | <li>最大:9151.11</li> |
| | | <li>平均:5255.09</li> |
| | | <li>中位数(P50):5099.93</li> |
| | | <li>P90:6211.76,P95:6406.13,P99:7407.30</li> |
| | | </ul> |
| | | |
| | | <h2>状态码分布</h2><ul><li>200: 10000</li></ul> |
| | | <h2>错误汇总</h2><p>无错误记录</p> |
| | | <h2>图表</h2><div><h3>latency_hist</h3><img src="动物房管理新建动物房压测任务_20251114_112009_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="动物房管理新建动物房压测任务_20251114_112009_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div> |
| | | |
| | | <h2>请求明细(仅显示前100条)</h2> |
| | | <table border="1" cellpadding="4" cellspacing="0"> |
| | | <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr> |
| | | <tr><td>3</td><td>2025-11-14 11:11:27</td><td>200</td><td>4755.320072174072</td><td>12</td><td></td></tr><tr><td>1</td><td>2025-11-14 11:11:27</td><td>200</td><td>4774.313926696777</td><td>12</td><td></td></tr><tr><td>6</td><td>2025-11-14 11:11:27</td><td>200</td><td>4778.31506729126</td><td>12</td><td></td></tr><tr><td>31</td><td>2025-11-14 11:11:27</td><td>200</td><td>4778.328418731689</td><td>12</td><td></td></tr><tr><td>10</td><td>2025-11-14 11:11:27</td><td>200</td><td>4789.316892623901</td><td>12</td><td></td></tr><tr><td>35</td><td>2025-11-14 11:11:27</td><td>200</td><td>4794.315814971924</td><td>12</td><td></td></tr><tr><td>12</td><td>2025-11-14 11:11:27</td><td>200</td><td>4802.316427230835</td><td>12</td><td></td></tr><tr><td>5</td><td>2025-11-14 11:11:27</td><td>200</td><td>4807.314872741699</td><td>12</td><td></td></tr><tr><td>14</td><td>2025-11-14 11:11:27</td><td>200</td><td>4811.316013336182</td><td>12</td><td></td></tr><tr><td>20</td><td>2025-11-14 11:11:27</td><td>200</td><td>4823.315382003784</td><td>12</td><td></td></tr><tr><td>24</td><td>2025-11-14 11:11:27</td><td>200</td><td>4822.31879234314</td><td>12</td><td></td></tr><tr><td>22</td><td>2025-11-14 11:11:27</td><td>200</td><td>4829.314708709717</td><td>12</td><td></td></tr><tr><td>33</td><td>2025-11-14 11:11:27</td><td>200</td><td>4827.317953109741</td><td>12</td><td></td></tr><tr><td>16</td><td>2025-11-14 11:11:27</td><td>200</td><td>4843.315839767456</td><td>12</td><td></td></tr><tr><td>4</td><td>2025-11-14 11:11:27</td><td>200</td><td>4852.316379547119</td><td>12</td><td></td></tr><tr><td>27</td><td>2025-11-14 11:11:27</td><td>200</td><td>4849.316596984863</td><td>12</td><td></td></tr><tr><td>41</td><td>2025-11-14 11:11:27</td><td>200</td><td>4846.314430236816</td><td>12</td><td></td></tr><tr><td>23</td><td>2025-11-14 11:11:27</td><td>200</td><td>4860.312700271606</td><td>12</td><td></td></tr><tr><td>53</td><td>2025-11-14 11:11:27</td><td>200</td><td>4861.315727233887</td><td>12</td><td></td></tr><tr><td>37</td><td>2025-11-14 11:11:27</td><td>200</td><td>4867.316961288452</td><td>12</td><td></td></tr><tr><td>25</td><td>2025-11-14 11:11:27</td><td>200</td><td>4879.315614700317</td><td>12</td><td></td></tr><tr><td>19</td><td>2025-11-14 11:11:27</td><td>200</td><td>4894.317150115967</td><td>12</td><td></td></tr><tr><td>30</td><td>2025-11-14 11:11:27</td><td>200</td><td>4891.315698623657</td><td>12</td><td></td></tr><tr><td>44</td><td>2025-11-14 11:11:27</td><td>200</td><td>4887.316465377808</td><td>12</td><td></td></tr><tr><td>15</td><td>2025-11-14 11:11:27</td><td>200</td><td>4902.318477630615</td><td>12</td><td></td></tr><tr><td>13</td><td>2025-11-14 11:11:27</td><td>200</td><td>4914.318561553955</td><td>12</td><td></td></tr><tr><td>50</td><td>2025-11-14 11:11:27</td><td>200</td><td>4903.316497802734</td><td>12</td><td></td></tr><tr><td>49</td><td>2025-11-14 11:11:27</td><td>200</td><td>4911.314964294434</td><td>12</td><td></td></tr><tr><td>40</td><td>2025-11-14 11:11:27</td><td>200</td><td>4915.313959121704</td><td>12</td><td></td></tr><tr><td>11</td><td>2025-11-14 11:11:27</td><td>200</td><td>4943.314790725708</td><td>12</td><td></td></tr><tr><td>32</td><td>2025-11-14 11:11:27</td><td>200</td><td>4948.315858840942</td><td>12</td><td></td></tr><tr><td>52</td><td>2025-11-14 11:11:27</td><td>200</td><td>4941.316843032837</td><td>12</td><td></td></tr><tr><td>29</td><td>2025-11-14 11:11:27</td><td>200</td><td>4963.313341140747</td><td>12</td><td></td></tr><tr><td>67</td><td>2025-11-14 11:11:27</td><td>200</td><td>4952.316999435425</td><td>12</td><td></td></tr><tr><td>42</td><td>2025-11-14 11:11:27</td><td>200</td><td>4967.312335968018</td><td>12</td><td></td></tr><tr><td>21</td><td>2025-11-14 11:11:27</td><td>200</td><td>4981.313705444336</td><td>12</td><td></td></tr><tr><td>51</td><td>2025-11-14 11:11:27</td><td>200</td><td>4985.3150844573975</td><td>12</td><td></td></tr><tr><td>69</td><td>2025-11-14 11:11:27</td><td>200</td><td>4982.314825057983</td><td>12</td><td></td></tr><tr><td>26</td><td>2025-11-14 11:11:27</td><td>200</td><td>5021.974325180054</td><td>12</td><td></td></tr><tr><td>28</td><td>2025-11-14 11:11:27</td><td>200</td><td>5021.504878997803</td><td>12</td><td></td></tr><tr><td>39</td><td>2025-11-14 11:11:27</td><td>200</td><td>5019.03772354126</td><td>12</td><td></td></tr><tr><td>7</td><td>2025-11-14 11:11:27</td><td>200</td><td>5036.609172821045</td><td>12</td><td></td></tr><tr><td>46</td><td>2025-11-14 11:11:27</td><td>200</td><td>5029.6080112457275</td><td>12</td><td></td></tr><tr><td>45</td><td>2025-11-14 11:11:27</td><td>200</td><td>5031.609773635864</td><td>12</td><td></td></tr><tr><td>17</td><td>2025-11-14 11:11:27</td><td>200</td><td>5040.610313415527</td><td>12</td><td></td></tr><tr><td>56</td><td>2025-11-14 11:11:27</td><td>200</td><td>5040.607929229736</td><td>12</td><td></td></tr><tr><td>60</td><td>2025-11-14 11:11:27</td><td>200</td><td>5042.60778427124</td><td>12</td><td></td></tr><tr><td>62</td><td>2025-11-14 11:11:27</td><td>200</td><td>5044.608116149902</td><td>12</td><td></td></tr><tr><td>65</td><td>2025-11-14 11:11:27</td><td>200</td><td>5053.607702255249</td><td>12</td><td></td></tr><tr><td>71</td><td>2025-11-14 11:11:27</td><td>200</td><td>5059.607744216919</td><td>12</td><td></td></tr><tr><td>9</td><td>2025-11-14 11:11:27</td><td>200</td><td>5080.612897872925</td><td>12</td><td></td></tr><tr><td>77</td><td>2025-11-14 11:11:27</td><td>200</td><td>5059.609651565552</td><td>12</td><td></td></tr><tr><td>86</td><td>2025-11-14 11:11:27</td><td>200</td><td>5062.608003616333</td><td>12</td><td></td></tr><tr><td>68</td><td>2025-11-14 11:11:27</td><td>200</td><td>5077.615737915039</td><td>12</td><td></td></tr><tr><td>82</td><td>2025-11-14 11:11:27</td><td>200</td><td>5078.694105148315</td><td>12</td><td></td></tr><tr><td>78</td><td>2025-11-14 11:11:27</td><td>200</td><td>5080.222129821777</td><td>12</td><td></td></tr><tr><td>58</td><td>2025-11-14 11:11:27</td><td>200</td><td>5088.218927383423</td><td>12</td><td></td></tr><tr><td>79</td><td>2025-11-14 11:11:27</td><td>200</td><td>5092.216014862061</td><td>12</td><td></td></tr><tr><td>88</td><td>2025-11-14 11:11:27</td><td>200</td><td>5091.216564178467</td><td>12</td><td></td></tr><tr><td>84</td><td>2025-11-14 11:11:27</td><td>200</td><td>5093.217134475708</td><td>12</td><td></td></tr><tr><td>83</td><td>2025-11-14 11:11:27</td><td>200</td><td>5097.214221954346</td><td>12</td><td></td></tr><tr><td>66</td><td>2025-11-14 11:11:27</td><td>200</td><td>5104.218006134033</td><td>12</td><td></td></tr><tr><td>96</td><td>2025-11-14 11:11:27</td><td>200</td><td>5097.215175628662</td><td>12</td><td></td></tr><tr><td>73</td><td>2025-11-14 11:11:27</td><td>200</td><td>5106.216192245483</td><td>12</td><td></td></tr><tr><td>72</td><td>2025-11-14 11:11:27</td><td>200</td><td>5121.214866638184</td><td>12</td><td></td></tr><tr><td>85</td><td>2025-11-14 11:11:27</td><td>200</td><td>5118.216037750244</td><td>12</td><td></td></tr><tr><td>99</td><td>2025-11-14 11:11:27</td><td>200</td><td>5118.216991424561</td><td>12</td><td></td></tr><tr><td>95</td><td>2025-11-14 11:11:27</td><td>200</td><td>5121.218204498291</td><td>12</td><td></td></tr><tr><td>34</td><td>2025-11-14 11:11:27</td><td>200</td><td>5140.2177810668945</td><td>12</td><td></td></tr><tr><td>81</td><td>2025-11-14 11:11:27</td><td>200</td><td>5131.2150955200195</td><td>12</td><td></td></tr><tr><td>100</td><td>2025-11-14 11:11:27</td><td>200</td><td>5126.216411590576</td><td>12</td><td></td></tr><tr><td>92</td><td>2025-11-14 11:11:27</td><td>200</td><td>5134.214162826538</td><td>12</td><td></td></tr><tr><td>80</td><td>2025-11-14 11:11:27</td><td>200</td><td>5139.2152309417725</td><td>12</td><td></td></tr><tr><td>63</td><td>2025-11-14 11:11:27</td><td>200</td><td>5145.216464996338</td><td>12</td><td></td></tr><tr><td>74</td><td>2025-11-14 11:11:27</td><td>200</td><td>5151.224613189697</td><td>12</td><td></td></tr><tr><td>75</td><td>2025-11-14 11:11:27</td><td>200</td><td>5153.218984603882</td><td>12</td><td></td></tr><tr><td>97</td><td>2025-11-14 11:11:27</td><td>200</td><td>5149.216413497925</td><td>12</td><td></td></tr><tr><td>55</td><td>2025-11-14 11:11:27</td><td>200</td><td>5163.216829299927</td><td>12</td><td></td></tr><tr><td>98</td><td>2025-11-14 11:11:27</td><td>200</td><td>5152.218341827393</td><td>12</td><td></td></tr><tr><td>59</td><td>2025-11-14 11:11:27</td><td>200</td><td>5176.215410232544</td><td>12</td><td></td></tr><tr><td>87</td><td>2025-11-14 11:11:27</td><td>200</td><td>5168.219327926636</td><td>12</td><td></td></tr><tr><td>36</td><td>2025-11-14 11:11:27</td><td>200</td><td>5186.21563911438</td><td>12</td><td></td></tr><tr><td>90</td><td>2025-11-14 11:11:27</td><td>200</td><td>5170.215606689453</td><td>12</td><td></td></tr><tr><td>70</td><td>2025-11-14 11:11:27</td><td>200</td><td>5178.2166957855225</td><td>12</td><td></td></tr><tr><td>91</td><td>2025-11-14 11:11:27</td><td>200</td><td>5176.214218139648</td><td>12</td><td></td></tr><tr><td>76</td><td>2025-11-14 11:11:27</td><td>200</td><td>5190.216302871704</td><td>12</td><td></td></tr><tr><td>8</td><td>2025-11-14 11:11:27</td><td>200</td><td>5214.215993881226</td><td>12</td><td></td></tr><tr><td>57</td><td>2025-11-14 11:11:27</td><td>200</td><td>5207.216024398804</td><td>12</td><td></td></tr><tr><td>2</td><td>2025-11-14 11:11:27</td><td>200</td><td>5255.218267440796</td><td>12</td><td></td></tr><tr><td>89</td><td>2025-11-14 11:11:27</td><td>200</td><td>5239.216327667236</td><td>12</td><td></td></tr><tr><td>93</td><td>2025-11-14 11:11:27</td><td>200</td><td>5274.2156982421875</td><td>12</td><td></td></tr><tr><td>38</td><td>2025-11-14 11:11:27</td><td>200</td><td>5383.217811584473</td><td>12</td><td></td></tr><tr><td>54</td><td>2025-11-14 11:11:27</td><td>200</td><td>5379.218578338623</td><td>12</td><td></td></tr><tr><td>43</td><td>2025-11-14 11:11:27</td><td>200</td><td>5412.217140197754</td><td>12</td><td></td></tr><tr><td>18</td><td>2025-11-14 11:11:27</td><td>200</td><td>5470.216512680054</td><td>12</td><td></td></tr><tr><td>48</td><td>2025-11-14 11:11:27</td><td>200</td><td>5502.215385437012</td><td>12</td><td></td></tr><tr><td>64</td><td>2025-11-14 11:11:27</td><td>200</td><td>5563.215732574463</td><td>12</td><td></td></tr><tr><td>47</td><td>2025-11-14 11:11:27</td><td>200</td><td>5583.215713500977</td><td>12</td><td></td></tr><tr><td>61</td><td>2025-11-14 11:11:28</td><td>200</td><td>5899.418354034424</td><td>12</td><td></td></tr><tr><td>94</td><td>2025-11-14 11:11:28</td><td>200</td><td>5927.4187088012695</td><td>12</td><td></td></tr> |
| | | </table> |
| | | |
| | | <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p> |
| | | </body> |
| | | </html> |
| | | |
| New file |
| | |
| | | """ |
| | | 集成压测脚本(带压测报告生成并通过钉钉发送摘要) |
| | | |
| | | 说明: |
| | | - 该脚本基于之前的稳定 worker/队列 实现,运行后会记录每条请求的时间、状态码和延迟。 |
| | | - 运行结束后会调用Util目录下的压测报告生成器(文件名: stress_test_report_generator.py),输出 HTML/JSON/CSV/(可选)DOCX 等文件。 |
| | | - 生成后会把关键统计摘要通过 DingTalk 机器人发送(调用 DingTalkHelper.send_message)。 |
| | | - 安装依赖:aiohttp, tqdm, numpy/pandas/matplotlib/python-docx(可选) |
| | | - 确保 DingTalkHelper 类在你的 `Util.dingtalk_helper` 中可用,且 ACCESS_TOKEN/SECRET 正确。 |
| | | """ |
| | | import sys |
| | | import os |
| | | # 将上一级目录加入模块搜索路径 |
| | | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| | | import asyncio |
| | | import aiohttp |
| | | import time |
| | | import traceback |
| | | import datetime |
| | | from tqdm import tqdm |
| | | from Util.random_util import RandomUtil |
| | | from Util.dingtalk_helper import DingTalkHelper |
| | | |
| | | |
| | | # --- 配置 --- |
| | | ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d' |
| | | SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19' |
| | | |
| | | apiname = "动物房管理新建动物房" |
| | | url = "http://tsinghua.baoyizn.com:9906/api/escrow/facilityroom/escrowRoom/save" |
| | | headers = { |
| | | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjMxNzQ4OTksInVzZXJuYW1lIjoiZ2x5In0.3TIHTLrOt8vWZxT0SWsjPjaCfBHPyYdv-469Irl7rpU", |
| | | "Content-Type": "application/json" |
| | | } |
| | | |
| | | NUM_WORKERS = 100 |
| | | TOTAL_REQUESTS = 10000 |
| | | MAX_RETRIES = 3 |
| | | REQUEST_TIMEOUT = 60 |
| | | OUTPUT_DIR = './load_test_report' |
| | | |
| | | # --- 初始化 --- |
| | | dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET) |
| | | |
| | | LARGE_CONTENT = "备注造数据" * 5 |
| | | FILES_PATH = "/userfiles/1463828311460319233/程序附件//baoyi/individual/individualrecord/2025/10/cs.jpg" |
| | | |
| | | |
| | | def create_animal_data(idx: int): |
| | | random_code = RandomUtil.generate_random_number_string(0, 10000) |
| | | random_code_grade = RandomUtil.generate_random_number_string(1, 2) |
| | | random_code_sex = RandomUtil.generate_random_number_string(1, 2) |
| | | random_date = RandomUtil.generate_random_date("2023-01-01", "2025-10-16") |
| | | return { |
| | | "status": "1", |
| | | "grade": { |
| | | "id": "1915600066719633410" |
| | | }, |
| | | "facilityId": "1915597153490292737", |
| | | "id": "", |
| | | "name": f"hyb测试房间{random_code},{random_date}", |
| | | "address": "", |
| | | "code": "", |
| | | "type": "3", |
| | | "roomType": "2", |
| | | "user": { |
| | | "id": "" |
| | | }, |
| | | "remarks": LARGE_CONTENT, |
| | | "parent": { |
| | | "id": "1915597153762922498", |
| | | "name": "" |
| | | }, |
| | | "parentIds": "0,1915597153490292737,1915597153762922498", |
| | | "sort": 1, |
| | | "createType": "1", |
| | | "researchGroup": { |
| | | "id": "" |
| | | } |
| | | } |
| | | |
| | | |
| | | async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES): |
| | | attempt = 0 |
| | | last_err = None |
| | | while attempt < max_retries: |
| | | data = create_animal_data(index) |
| | | start = time.time() |
| | | try: |
| | | async with session.post(url, json=data, headers=headers) as resp: |
| | | text = await resp.text() |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | status = resp.status |
| | | if status == 200: |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': status, |
| | | 'latency_ms': latency_ms, |
| | | 'response_size': len(text) if text is not None else None, |
| | | 'error': None |
| | | } |
| | | else: |
| | | last_err = f'status_{status}:{text}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | except Exception as e: |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | last_err = f'{type(e).__name__}:{str(e)}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | # 最终失败 |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': 0, |
| | | 'latency_ms': latency_ms if 'latency_ms' in locals() else 0, |
| | | 'response_size': None, |
| | | 'error': last_err |
| | | } |
| | | |
| | | |
| | | async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock): |
| | | while True: |
| | | idx = await queue.get() |
| | | if idx is None: |
| | | queue.task_done() |
| | | break |
| | | try: |
| | | res = await perform_request(session, idx) |
| | | # 记录到报告生成器 |
| | | gen.record_result( |
| | | index=res['index'], |
| | | timestamp=res['timestamp'], |
| | | status_code=int(res['status_code']), |
| | | latency_ms=float(res['latency_ms']), |
| | | response_size=res.get('response_size'), |
| | | error=res.get('error') |
| | | ) |
| | | async with lock: |
| | | if res['status_code'] and 200 <= res['status_code'] < 300: |
| | | success_counter['count'] += 1 |
| | | else: |
| | | failed_list.append((res['index'], res.get('error'))) |
| | | pbar.update(1) |
| | | except Exception as e: |
| | | async with lock: |
| | | failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}')) |
| | | pbar.update(1) |
| | | finally: |
| | | queue.task_done() |
| | | |
| | | |
| | | async def batch_create_animals(total: int, num_workers: int): |
| | | # 动态加载报告生成器模块(支持中文文件名) |
| | | gen = None |
| | | try: |
| | | import importlib.util |
| | | script_dir = os.path.dirname(os.path.abspath(__file__)) |
| | | report_path = os.path.join(script_dir, 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py') |
| | | if os.path.exists(report_path): |
| | | spec = importlib.util.spec_from_file_location('report_module', report_path) |
| | | report_module = importlib.util.module_from_spec(spec) |
| | | spec.loader.exec_module(report_module) |
| | | LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator') |
| | | else: |
| | | # 备用:尝试直接导入模块名(若你的文件名已改为 ascii) |
| | | from report_generator import LoadTestReportGenerator # type: ignore |
| | | gen = LoadTestReportGenerator(test_name=f'{apiname}压测任务', report_title='压测详细报告') |
| | | except Exception as e: |
| | | print('无法加载压测报告生成器,请确认stress_test_report_generator.py 文件位置正确。\n', e) |
| | | raise |
| | | |
| | | timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT) |
| | | connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False) |
| | | async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session: |
| | | queue = asyncio.Queue() |
| | | for i in range(1, total + 1): |
| | | await queue.put(i) |
| | | for _ in range(num_workers): |
| | | await queue.put(None) |
| | | |
| | | success_counter = {'count': 0} |
| | | failed_list = [] |
| | | lock = asyncio.Lock() |
| | | |
| | | with tqdm(total=total, desc='创建进度') as pbar: |
| | | workers = [ |
| | | asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock)) |
| | | for i in range(num_workers) |
| | | ] |
| | | await asyncio.gather(*workers) |
| | | |
| | | # 任务完成,生成报告 |
| | | os.makedirs(OUTPUT_DIR, exist_ok=True) |
| | | outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx']) |
| | | |
| | | stats = gen.compute_stats() |
| | | |
| | | # 构造钉钉摘要消息(中文) |
| | | now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
| | | msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}'] |
| | | msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}") |
| | | msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}") |
| | | lat = stats.get('latency_ms', {}) |
| | | msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}") |
| | | |
| | | # 列出生成的报告文件 |
| | | file_list = [] |
| | | for k, v in outputs.items(): |
| | | if k == 'charts': |
| | | for cname, cpath in v.items(): |
| | | file_list.append(os.path.abspath(cpath)) |
| | | else: |
| | | file_list.append(os.path.abspath(v)) |
| | | msg.append('生成文件:') |
| | | for p in file_list: |
| | | msg.append(p) |
| | | |
| | | final_msg = '\n'.join(msg) |
| | | |
| | | # 发送钉钉消息 |
| | | try: |
| | | dingtalk_helper.send_message(final_msg) |
| | | except Exception as e: |
| | | print('发送钉钉消息失败:', e) |
| | | |
| | | print('\n[SUMMARY] 已生成报告并发送钉钉摘要。') |
| | | print('成功数:', success_counter['count'], ' 失败数:', len(failed_list)) |
| | | if failed_list: |
| | | print('失败示例(最多显示50条):') |
| | | for idx, err in failed_list[:50]: |
| | | print(f' #{idx} => {err}') |
| | | |
| | | |
| | | if __name__ == '__main__': |
| | | # 运行前建议先用小规模测试 |
| | | TOTAL_REQUESTS = TOTAL_REQUESTS |
| | | NUM_WORKERS = NUM_WORKERS |
| | | asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS)) |
| New file |
| | |
| | | import asyncio |
| | | import aiohttp |
| | | import traceback |
| | | import time |
| | | from Util.random_util import RandomUtil # 保证该模块中提供了生成随机数字和随机日期的函数 |
| | | from Util.dingtalk_helper import DingTalkHelper |
| | | from tqdm import tqdm # 导入进度条库 |
| | | |
| | | # 钉钉机器人 access_token 和 secret |
| | | ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d' |
| | | SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19' |
| | | |
| | | # 请求URL和请求头 |
| | | apiname = "我的动物" |
| | | url = "http://tsinghua.baoyizn.com:9906/api/escrow/animal/escrowAnimal/save" |
| | | headers = { |
| | | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDUwMzExNjAsInVzZXJuYW1lIjoiZ2x5In0.zra2vwwvl-NQKN_tyNzRbiF3kzfUQovXBuSmEGcXPFY", |
| | | "Content-Type": "application/json" |
| | | } |
| | | |
| | | dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET) |
| | | |
| | | gender = "1" |
| | | mother_id = "" |
| | | mother_wholeCode = "" |
| | | father_id = "" |
| | | father_wholeCode = "" |
| | | algebra = 1 |
| | | preCode = "性能测试" |
| | | |
| | | # 生成请求体 |
| | | def create_animal_data(): |
| | | random_code = RandomUtil.generate_random_number_string(0, 999999999) |
| | | random_date = RandomUtil.generate_random_date("2023-01-01", "2025-03-25") |
| | | return { |
| | | "bac": {"id": ""}, |
| | | "cage": {"id": "", "code": ""}, |
| | | "code": random_code, |
| | | "room": {"id": ""}, |
| | | "user": { |
| | | "id": "1780483551554764801", |
| | | "name": "hyb实验人员" |
| | | }, |
| | | "color": "4", |
| | | "ethic": { |
| | | "id": "1790288720729997312", |
| | | "apCode": "24- Hybktfzr23.G24-4" |
| | | }, |
| | | "shelf": { |
| | | "id": "", |
| | | "variety": {"id": ""} |
| | | }, |
| | | "father": { |
| | | "id": father_id, |
| | | "wholeCode": father_wholeCode |
| | | }, |
| | | "gender": gender, |
| | | "mother": { |
| | | "id": mother_id, |
| | | "wholeCode": mother_wholeCode |
| | | }, |
| | | "strain": { |
| | | "id": "1846491449918550017", |
| | | "bac": {"id": "", "name": "转基因"}, |
| | | "name": "hyb无需鉴定基因型,求求勿动", |
| | | "type": "1", |
| | | "maxNum": 5, |
| | | "geneIds": {"id": "1810583565757919233,1810583743961313281,1810583743961313281,1810583743961313281,1860972233362567169"}, |
| | | "variety": { |
| | | "id": "", |
| | | "name": "SPF级基因鼠(荧光蛋白标记小鼠)", |
| | | "isChip": "1" |
| | | } |
| | | }, |
| | | "algebra": algebra, |
| | | "preCode": preCode , |
| | | "variety": {"id": "1661975536839733250"}, |
| | | "weaning": "2", |
| | | "chipCode": random_code, |
| | | "genotype": "hyb管理员创建共享,求求勿动 +/-/Tg/WT,hyb管理员创建使用者为课题负责人共享,求求勿动 +/-/Homo/Heter/WT", |
| | | "transfer": "0", |
| | | "birthDate": random_date, |
| | | "wholeCode": f"{preCode}-{random_code}", |
| | | "algebraWay": "1", |
| | | "animalStatus": "0", |
| | | "escrowStatus": "1", |
| | | "researchGroup": {"id": "1699404304041828353"} |
| | | } |
| | | |
| | | |
| | | # 异步发送单个请求,并返回是否成功 |
| | | async def create_animal(session: aiohttp.ClientSession, index: int, max_retries: int = 3): |
| | | data = create_animal_data() |
| | | attempt = 0 |
| | | while attempt < max_retries: |
| | | try: |
| | | async with session.post(url, json=data, headers=headers) as response: |
| | | resp_text = await response.text() |
| | | if response.status == 200: |
| | | print(f"[INFO] 第 {index} 个动物创建成功,响应:{resp_text}") |
| | | return True |
| | | else: |
| | | print(f"[ERROR] 第 {index} 个动物创建失败,状态码:{response.status},响应:{resp_text}") |
| | | return False |
| | | except Exception as e: |
| | | attempt += 1 |
| | | status_code = getattr(e, 'status', 'N/A') |
| | | error_trace = traceback.format_exc() |
| | | full_log = ( |
| | | f"请求URL: {url}\n" |
| | | f"请求头: {headers}\n" |
| | | f"请求数据: {data}\n" |
| | | f"状态码: {status_code}\n" |
| | | f"异常信息: {str(e)}\n" |
| | | f"堆栈跟踪:\n{error_trace}\n" |
| | | f"重试次数: {attempt}/{max_retries}" |
| | | ) |
| | | print(f"[EXCEPTION] 第 {index} 个动物请求异常:\n{full_log}\n") |
| | | if attempt < max_retries: |
| | | # 重试前等待 1 秒 |
| | | await asyncio.sleep(1) |
| | | else: |
| | | return False |
| | | |
| | | |
| | | # 批量异步创建动物信息,同时显示进度条 |
| | | async def batch_create_animals(batch_size: int): |
| | | tasks = [] |
| | | # 设置超时时间为60秒(可根据需要调整) |
| | | timeout = aiohttp.ClientTimeout(total=60) |
| | | async with aiohttp.ClientSession(timeout=timeout) as session: |
| | | for i in range(1, batch_size + 1): |
| | | task = asyncio.ensure_future(create_animal(session, i)) |
| | | tasks.append(task) |
| | | |
| | | success_count = 0 |
| | | # 使用 asyncio.as_completed 逐个等待任务完成,并更新进度条 |
| | | with tqdm(total=batch_size, desc="创建动物进度") as pbar: |
| | | for finished in asyncio.as_completed(tasks): |
| | | result = await finished |
| | | if result: |
| | | success_count += 1 |
| | | pbar.update(1) |
| | | summary = f"\n[SUMMARY] 成功创建 {success_count}/{batch_size} 只动物" |
| | | print(summary) |
| | | dingtalk_helper.send_message( |
| | | f"你的批量创建{apiname}模块数据已完成:成功创建 {success_count}/{batch_size} 只动物") |
| | | |
| | | |
| | | # 主入口 |
| | | if __name__ == '__main__': |
| | | n = 10 # 可根据需要调整批量创建数量 |
| | | asyncio.run(batch_create_animals(n)) |
| New file |
| | |
| | | """ |
| | | pad笼位更新压测脚本(带压测报告生成并通过钉钉发送摘要) |
| | | |
| | | 说明: |
| | | - 该脚本模拟多个饲养员同时对笼位进行频繁更新操作 |
| | | - 从userinfo.xlsx读取多个用户token实现多用户并发压测 |
| | | - 从数据库动态获取笼位ID、用户ID和课题组ID |
| | | - 运行后会记录每条请求的时间、状态码和延迟 |
| | | - 运行结束后会生成压测报告并通过钉钉发送摘要 |
| | | """ |
| | | |
| | | import sys |
| | | import os |
| | | # 将上一级目录加入模块搜索路径 |
| | | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| | | import asyncio |
| | | import aiohttp |
| | | import time |
| | | import traceback |
| | | import datetime |
| | | import pandas as pd |
| | | import pymysql |
| | | import random |
| | | from tqdm import tqdm |
| | | from Util.dingtalk_helper import DingTalkHelper |
| | | |
| | | # 将上一级目录加入模块搜索路径 |
| | | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| | | |
| | | # --- 配置 --- |
| | | ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d' |
| | | SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19' |
| | | |
| | | apiname = "pda笼位更新" |
| | | url = "http://qilu.baoyizn.com:9935/api/base/cage/cage/trainingCage" |
| | | headers = { |
| | | "user-agent": "Mozilla/5.0 (Linux; Android 10; DS-MDT301 Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.186 Mobile Safari/537.36 uni-app Html5Plus/1.0 (Immersed/24.0)", |
| | | "Content-Type": "application/json; charset=utf-8", |
| | | "Accept-Encoding": "gzip" |
| | | } |
| | | |
| | | NUM_WORKERS = 100 |
| | | TOTAL_REQUESTS = 20000 |
| | | MAX_RETRIES = 3 |
| | | REQUEST_TIMEOUT = 60 |
| | | OUTPUT_DIR = './load_test_report' |
| | | |
| | | # --- 数据库配置 --- |
| | | # DB_CONFIG = { |
| | | # 'host': 'rm-wz97y18i4t16hfsk6qo.mysql.rds.aliyuncs.com', |
| | | # 'database': 'srps_qilu', |
| | | # 'user': 'dev', |
| | | # 'password': 'Hello@112', |
| | | # 'charset': 'utf8mb4' |
| | | # } |
| | | DB_CONFIG = { |
| | | 'host': 'rm-wz90i533p18631e685o.mysql.rds.aliyuncs.com', |
| | | 'database': 'srps_qilu_prod', |
| | | 'user': 'dev', |
| | | 'password': 'Hello@112', |
| | | 'charset': 'utf8mb4' |
| | | } |
| | | |
| | | # --- 初始化 --- |
| | | dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET) |
| | | |
| | | class DataManager: |
| | | """数据管理器,负责从数据库和Excel加载数据""" |
| | | |
| | | def __init__(self): |
| | | self.cage_ids = [] |
| | | self.user_data = [] # 存储用户ID和对应的课题组ID |
| | | self.user_tokens = [] # 存储用户token |
| | | |
| | | def load_user_tokens(self, excel_file='TestData/userinfo.xlsx'): |
| | | """从Excel加载用户token""" |
| | | try: |
| | | df = pd.read_excel(excel_file) |
| | | self.user_tokens = df['token'].tolist() |
| | | print(f"成功加载 {len(self.user_tokens)} 个用户token") |
| | | return True |
| | | except Exception as e: |
| | | print(f"加载用户token失败: {e}") |
| | | return False |
| | | |
| | | def load_database_data(self): |
| | | """从数据库加载笼位ID和用户信息""" |
| | | try: |
| | | conn = pymysql.connect(**DB_CONFIG) |
| | | |
| | | # 获取笼位ID |
| | | with conn.cursor() as cursor: |
| | | cursor.execute("SELECT id FROM l_cage WHERE id IS NOT NULL") |
| | | self.cage_ids = [row[0] for row in cursor.fetchall()] |
| | | print(f"成功加载 {len(self.cage_ids)} 个笼位ID") |
| | | |
| | | # 获取用户ID和课题组ID |
| | | with conn.cursor() as cursor: |
| | | cursor.execute("SELECT id, research_group_ids FROM sys_user WHERE research_group_ids IS NOT NULL AND research_group_ids != ''") |
| | | |
| | | for user_id, research_groups in cursor.fetchall(): |
| | | # 处理多个课题组ID(用逗号分隔的情况) |
| | | if research_groups and ',' in research_groups: |
| | | group_ids = [gid.strip() for gid in research_groups.split(',') if gid.strip()] |
| | | elif research_groups: |
| | | group_ids = [research_groups.strip()] |
| | | else: |
| | | continue |
| | | |
| | | self.user_data.append({ |
| | | 'user_id': user_id, |
| | | 'research_group_ids': group_ids |
| | | }) |
| | | |
| | | print(f"成功加载 {len(self.user_data)} 个有效用户数据") |
| | | |
| | | conn.close() |
| | | return len(self.cage_ids) > 0 and len(self.user_data) > 0 |
| | | |
| | | except Exception as e: |
| | | print(f"数据库连接失败: {e}") |
| | | return False |
| | | |
| | | def get_random_cage_id(self): |
| | | """随机获取一个笼位ID""" |
| | | return random.choice(self.cage_ids) if self.cage_ids else None |
| | | |
| | | def get_random_user_data(self): |
| | | """随机获取一个用户数据(用户ID和课题组ID)""" |
| | | if not self.user_data: |
| | | return None, None |
| | | |
| | | user = random.choice(self.user_data) |
| | | user_id = user['user_id'] |
| | | research_group_id = random.choice(user['research_group_ids']) |
| | | |
| | | return user_id, research_group_id |
| | | |
| | | def get_random_token(self): |
| | | """随机获取一个用户token""" |
| | | return random.choice(self.user_tokens) if self.user_tokens else None |
| | | |
| | | # 全局数据管理器 |
| | | data_manager = DataManager() |
| | | |
| | | def create_cage_data(): |
| | | """创建笼位更新请求数据""" |
| | | cage_id = data_manager.get_random_cage_id() |
| | | user_id, research_group_id = data_manager.get_random_user_data() |
| | | |
| | | if not cage_id or not user_id or not research_group_id: |
| | | return None |
| | | |
| | | return { |
| | | "cage": {"id": str(cage_id)}, |
| | | "researchGroup": {"id": research_group_id}, |
| | | "user": {"id": user_id} |
| | | } |
| | | |
| | | async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES): |
| | | """执行单个请求""" |
| | | attempt = 0 |
| | | last_err = None |
| | | token = data_manager.get_random_token() |
| | | |
| | | if not token: |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': 0, |
| | | 'latency_ms': 0, |
| | | 'response_size': None, |
| | | 'error': 'No available token' |
| | | } |
| | | |
| | | # 动态设置token到headers |
| | | current_headers = headers.copy() |
| | | current_headers['token'] = token |
| | | |
| | | while attempt < max_retries: |
| | | data = create_cage_data() |
| | | if not data: |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': 0, |
| | | 'latency_ms': 0, |
| | | 'response_size': None, |
| | | 'error': 'No available cage/user data' |
| | | } |
| | | |
| | | start = time.time() |
| | | try: |
| | | async with session.post(url, json=data, headers=current_headers) as resp: |
| | | text = await resp.text() |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | status = resp.status |
| | | if status == 200: |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': status, |
| | | 'latency_ms': latency_ms, |
| | | 'response_size': len(text) if text is not None else None, |
| | | 'error': None |
| | | } |
| | | else: |
| | | last_err = f'status_{status}:{text}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | except Exception as e: |
| | | latency_ms = (time.time() - start) * 1000.0 |
| | | last_err = f'{type(e).__name__}:{str(e)}' |
| | | attempt += 1 |
| | | await asyncio.sleep(min(10, 2 ** attempt)) |
| | | |
| | | # 最终失败 |
| | | return { |
| | | 'index': index, |
| | | 'timestamp': time.time(), |
| | | 'status_code': 0, |
| | | 'latency_ms': latency_ms if 'latency_ms' in locals() else 0, |
| | | 'response_size': None, |
| | | 'error': last_err |
| | | } |
| | | |
| | | async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock): |
| | | """工作线程""" |
| | | while True: |
| | | idx = await queue.get() |
| | | if idx is None: |
| | | queue.task_done() |
| | | break |
| | | try: |
| | | res = await perform_request(session, idx) |
| | | # 记录到报告生成器 |
| | | if gen: |
| | | gen.record_result( |
| | | index=res['index'], |
| | | timestamp=res['timestamp'], |
| | | status_code=int(res['status_code']), |
| | | latency_ms=float(res['latency_ms']), |
| | | response_size=res.get('response_size'), |
| | | error=res.get('error') |
| | | ) |
| | | async with lock: |
| | | if res['status_code'] and 200 <= res['status_code'] < 300: |
| | | success_counter['count'] += 1 |
| | | else: |
| | | failed_list.append((res['index'], res.get('error'))) |
| | | pbar.update(1) |
| | | except Exception as e: |
| | | async with lock: |
| | | failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}')) |
| | | pbar.update(1) |
| | | finally: |
| | | queue.task_done() |
| | | |
| | | async def batch_update_cages(total: int, num_workers: int): |
| | | """批量更新笼位""" |
| | | # 动态加载报告生成器模块 |
| | | gen = None |
| | | try: |
| | | import importlib.util |
| | | |
| | | # 使用您提供的绝对路径 |
| | | report_path = 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py' |
| | | |
| | | if os.path.exists(report_path): |
| | | spec = importlib.util.spec_from_file_location('report_module', report_path) |
| | | report_module = importlib.util.module_from_spec(spec) |
| | | spec.loader.exec_module(report_module) |
| | | LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator') |
| | | gen = LoadTestReportGenerator(test_name=f'{apiname}压测任务', report_title='压测详细报告') |
| | | print(f"成功加载压测报告生成器: {report_path}") |
| | | else: |
| | | # 备用:尝试相对路径 |
| | | script_dir = os.path.dirname(os.path.abspath(__file__)) |
| | | report_path = os.path.join(script_dir, 'Util', 'stress_test_report_generator.py') |
| | | if os.path.exists(report_path): |
| | | spec = importlib.util.spec_from_file_location('report_module', report_path) |
| | | report_module = importlib.util.module_from_spec(spec) |
| | | spec.loader.exec_module(report_module) |
| | | LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator') |
| | | gen = LoadTestReportGenerator(test_name=f'{apiname}压测任务', report_title='压测详细报告') |
| | | print(f"成功加载压测报告生成器: {report_path}") |
| | | else: |
| | | print('警告: 未找到压测报告生成器,将跳过报告生成') |
| | | except Exception as e: |
| | | print(f'加载压测报告生成器失败: {e},将跳过报告生成') |
| | | |
| | | # 初始化数据 |
| | | if not data_manager.load_user_tokens(): |
| | | print("加载用户token失败,退出压测") |
| | | return |
| | | |
| | | if not data_manager.load_database_data(): |
| | | print("加载数据库数据失败,退出压测") |
| | | return |
| | | |
| | | timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT) |
| | | connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False) |
| | | |
| | | async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session: |
| | | queue = asyncio.Queue() |
| | | for i in range(1, total + 1): |
| | | await queue.put(i) |
| | | for _ in range(num_workers): |
| | | await queue.put(None) |
| | | |
| | | success_counter = {'count': 0} |
| | | failed_list = [] |
| | | lock = asyncio.Lock() |
| | | |
| | | with tqdm(total=total, desc='压测进度') as pbar: |
| | | workers = [ |
| | | asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock)) |
| | | for i in range(num_workers) |
| | | ] |
| | | await asyncio.gather(*workers) |
| | | |
| | | # 任务完成,生成报告 |
| | | if gen: |
| | | os.makedirs(OUTPUT_DIR, exist_ok=True) |
| | | outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx']) |
| | | stats = gen.compute_stats() |
| | | |
| | | # 构造钉钉摘要消息 |
| | | now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
| | | msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}'] |
| | | msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}") |
| | | |
| | | if 'duration_seconds' in stats: |
| | | msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}") |
| | | |
| | | if 'latency_ms' in stats: |
| | | lat = stats.get('latency_ms', {}) |
| | | msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}") |
| | | |
| | | msg.append(f"使用用户数:{len(data_manager.user_tokens)}") |
| | | msg.append(f"可用笼位数:{len(data_manager.cage_ids)}") |
| | | msg.append(f"有效用户数:{len(data_manager.user_data)}") |
| | | |
| | | # 列出生成的报告文件 |
| | | if outputs: |
| | | file_list = [] |
| | | for k, v in outputs.items(): |
| | | if k == 'charts': |
| | | for cname, cpath in v.items(): |
| | | file_list.append(os.path.abspath(cpath)) |
| | | else: |
| | | file_list.append(os.path.abspath(v)) |
| | | msg.append('生成文件:') |
| | | for p in file_list: |
| | | msg.append(p) |
| | | |
| | | final_msg = '\n'.join(msg) |
| | | else: |
| | | # 如果没有报告生成器,计算基础统计 |
| | | stats = { |
| | | 'total_requests': total, |
| | | 'success_count': success_counter['count'], |
| | | 'fail_count': len(failed_list), |
| | | 'success_rate': success_counter['count'] / total if total > 0 else 0 |
| | | } |
| | | |
| | | # 基础统计消息 |
| | | now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
| | | msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}'] |
| | | msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}") |
| | | msg.append(f"使用用户数:{len(data_manager.user_tokens)}") |
| | | msg.append(f"可用笼位数:{len(data_manager.cage_ids)}") |
| | | msg.append(f"有效用户数:{len(data_manager.user_data)}") |
| | | final_msg = '\n'.join(msg) |
| | | |
| | | # 发送钉钉消息 |
| | | try: |
| | | dingtalk_helper.send_message(final_msg) |
| | | print('钉钉消息发送成功') |
| | | except Exception as e: |
| | | print('发送钉钉消息失败:', e) |
| | | |
| | | print('\n[压测摘要]') |
| | | print('成功数:', success_counter['count'], ' 失败数:', len(failed_list)) |
| | | if failed_list: |
| | | print('失败示例(最多显示20条):') |
| | | for idx, err in failed_list[:20]: |
| | | print(f' #{idx} => {err}') |
| | | |
| | | if __name__ == '__main__': |
| | | # 运行前检查依赖 |
| | | try: |
| | | import pymysql |
| | | import pandas |
| | | print("依赖检查通过,开始压测...") |
| | | except ImportError as e: |
| | | print(f"缺少依赖: {e}") |
| | | print("请安装: pip install pymysql pandas") |
| | | exit(1) |
| | | |
| | | # 运行压测 |
| | | asyncio.run(batch_update_cages(TOTAL_REQUESTS, NUM_WORKERS)) |