From eda094b0aa223b1ba62747839c96654c1bd004ca Mon Sep 17 00:00:00 2001 From: ljxpython <1030470148@qq.com> Date: Sun, 6 Jul 2025 09:27:51 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E5=A2=9E=E5=BC=BAlocust=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=8A=9F=E8=83=BD,=E4=BA=8C=E6=9C=9F=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 6148 -> 6148 bytes README.md | 552 ++++++++++- docs/.DS_Store | Bin 6148 -> 6148 bytes docs/README.md | 105 ++ docs/api/analysis.md | 550 +++++++++++ docs/api/data-manager.md | 659 +++++++++++++ docs/api/load-shapes.md | 609 ++++++++++++ docs/api/monitoring.md | 620 ++++++++++++ docs/api/plugins.md | 694 +++++++++++++ docs/api/utilities.md | 933 ++++++++++++++++++ docs/architecture/data-flow.md | 407 ++++++++ docs/architecture/modules.md | 238 +++++ docs/architecture/overview.md | 567 +++++++++++ docs/architecture/plugin-system.md | 440 +++++++++ .../assets}/image-20250531202135855.png | Bin .../assets}/image-20250531205049805.png | Bin .../assets}/image-20250531205141426.png | Bin .../assets}/image-20250531211159524.png | Bin .../assets}/image-20250531211606483.png | Bin .../assets}/image-20250531212549739.png | Bin .../assets}/image-20250601103247786.png | Bin .../assets}/image-20250601103425608.png | Bin .../assets}/image-20250601103515708.png | Bin docs/configuration/development.md | 818 +++++++++++++++ docs/configuration/distributed.md | 761 ++++++++++++++ docs/configuration/framework-config.md | 574 +++++++++++ docs/configuration/monitoring-config.md | 917 +++++++++++++++++ docs/configuration/notification-config.md | 798 +++++++++++++++ docs/configuration/plugin-config.md | 618 ++++++++++++ docs/configuration/production.md | 858 ++++++++++++++++ docs/configuration/testing.md | 933 ++++++++++++++++++ docs/development/coding-standards.md | 781 +++++++++++++++ docs/development/contributing.md | 732 ++++++++++++++ docs/development/documentation-standards.md | 430 ++++++++ docs/development/load-shape-development.md | 729 ++++++++++++++ docs/development/plugin-development.md | 692 +++++++++++++ docs/development/setup.md | 608 ++++++++++++ docs/development/testing.md | 733 ++++++++++++++ docs/examples/advanced-examples.md | 625 ++++++++++++ docs/examples/advanced-usage.md | 548 ++++++++++ docs/examples/basic-examples.md | 707 +++++++++++++ docs/examples/best-practices.md | 730 ++++++++++++++ docs/examples/faq.md | 487 +++++++++ docs/examples/troubleshooting.md | 793 +++++++++++++++ docs/getting-started/concepts.md | 433 ++++++++ docs/getting-started/first-test.md | 440 +++++++++ docs/getting-started/installation.md | 318 ++++++ docs/getting-started/quickstart.md | 402 ++++++++ locustfiles/shape_classes/__init__.py | 31 + locustfiles/shape_classes/advanced_shapes.py | 431 ++++++++ plugin_config.template.json | 130 +++ src/.DS_Store | Bin 0 -> 6148 bytes src/analysis/__init__.py | 15 + src/analysis/performance_analyzer.py | 244 +++++ src/analysis/report_generator.py | 443 +++++++++ src/analysis/trend_analyzer.py | 326 ++++++ src/client/.DS_Store | Bin 0 -> 6148 bytes src/data_manager/__init__.py | 11 + src/data_manager/data_distributor.py | 384 +++++++ src/data_manager/data_generator.py | 394 ++++++++ src/data_manager/data_provider.py | 429 ++++++++ src/monitoring/__init__.py | 19 + src/monitoring/alert_manager.py | 455 +++++++++ src/monitoring/notification_service.py | 356 +++++++ src/monitoring/performance_monitor.py | 332 +++++++ src/plugins/__init__.py | 18 + src/plugins/builtin/__init__.py | 5 + src/plugins/builtin/csv_report_plugin.py | 258 +++++ src/plugins/plugin_interface.py | 333 +++++++ src/plugins/plugin_loader.py | 352 +++++++ src/plugins/plugin_manager.py | 348 +++++++ src/utils/robot.py | 241 ++++- 72 files changed, 28381 insertions(+), 13 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/api/analysis.md create mode 100644 docs/api/data-manager.md create mode 100644 docs/api/load-shapes.md create mode 100644 docs/api/monitoring.md create mode 100644 docs/api/plugins.md create mode 100644 docs/api/utilities.md create mode 100644 docs/architecture/data-flow.md create mode 100644 docs/architecture/modules.md create mode 100644 docs/architecture/overview.md create mode 100644 docs/architecture/plugin-system.md rename {assets => docs/assets}/image-20250531202135855.png (100%) rename {assets => docs/assets}/image-20250531205049805.png (100%) rename {assets => docs/assets}/image-20250531205141426.png (100%) rename {assets => docs/assets}/image-20250531211159524.png (100%) rename {assets => docs/assets}/image-20250531211606483.png (100%) rename {assets => docs/assets}/image-20250531212549739.png (100%) rename {assets => docs/assets}/image-20250601103247786.png (100%) rename {assets => docs/assets}/image-20250601103425608.png (100%) rename {assets => docs/assets}/image-20250601103515708.png (100%) create mode 100644 docs/configuration/development.md create mode 100644 docs/configuration/distributed.md create mode 100644 docs/configuration/framework-config.md create mode 100644 docs/configuration/monitoring-config.md create mode 100644 docs/configuration/notification-config.md create mode 100644 docs/configuration/plugin-config.md create mode 100644 docs/configuration/production.md create mode 100644 docs/configuration/testing.md create mode 100644 docs/development/coding-standards.md create mode 100644 docs/development/contributing.md create mode 100644 docs/development/documentation-standards.md create mode 100644 docs/development/load-shape-development.md create mode 100644 docs/development/plugin-development.md create mode 100644 docs/development/setup.md create mode 100644 docs/development/testing.md create mode 100644 docs/examples/advanced-examples.md create mode 100644 docs/examples/advanced-usage.md create mode 100644 docs/examples/basic-examples.md create mode 100644 docs/examples/best-practices.md create mode 100644 docs/examples/faq.md create mode 100644 docs/examples/troubleshooting.md create mode 100644 docs/getting-started/concepts.md create mode 100644 docs/getting-started/first-test.md create mode 100644 docs/getting-started/installation.md create mode 100644 docs/getting-started/quickstart.md create mode 100644 locustfiles/shape_classes/advanced_shapes.py create mode 100644 plugin_config.template.json create mode 100644 src/.DS_Store create mode 100644 src/analysis/__init__.py create mode 100644 src/analysis/performance_analyzer.py create mode 100644 src/analysis/report_generator.py create mode 100644 src/analysis/trend_analyzer.py create mode 100644 src/client/.DS_Store create mode 100644 src/data_manager/__init__.py create mode 100644 src/data_manager/data_distributor.py create mode 100644 src/data_manager/data_generator.py create mode 100644 src/data_manager/data_provider.py create mode 100644 src/monitoring/__init__.py create mode 100644 src/monitoring/alert_manager.py create mode 100644 src/monitoring/notification_service.py create mode 100644 src/monitoring/performance_monitor.py create mode 100644 src/plugins/__init__.py create mode 100644 src/plugins/builtin/__init__.py create mode 100644 src/plugins/builtin/csv_report_plugin.py create mode 100644 src/plugins/plugin_interface.py create mode 100644 src/plugins/plugin_loader.py create mode 100644 src/plugins/plugin_manager.py diff --git a/.DS_Store b/.DS_Store index b1bc858e925d93fb375ed4bdb59c7154b9b72c92..658fd7670f278f0041c9d0613ce0570752195c2b 100644 GIT binary patch delta 200 zcmZoMXfc=|&e%RNQH+&?fq{WzVxov76OaJ{EI`c2z#zboQk 基于locust框架进行二次开发:https://locust.io/ +[![Python Version](https://img.shields.io/badge/python-3.7%2B-blue.svg)](https://python.org) +[![Locust Version](https://img.shields.io/badge/locust-2.0%2B-green.svg)](https://locust.io) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Documentation](https://img.shields.io/badge/docs-latest-brightgreen.svg)](docs/README.md) + +> 🎯 **企业级性能测试解决方案** - 基于Locust框架的二次开发,支持分层架构、松耦合、高内聚的设计原则 + +一个功能强大、易于扩展的性能测试框架,专为现代化测试需求而设计。框架采用插件化架构,支持多种负载模式、实时监控、智能分析和多渠道告警,让性能测试变得更加简单高效。 + +## ✨ 核心特性 + +### 🎯 **测试执行增强** +- **集合点功能** - 支持并发事务同步执行 +- **自定义事务** - 灵活的事务统计和报告 +- **自定义参数** - 动态参数传递和配置 +- **优雅停止** - 智能的测试终止机制 + +### 📊 **数据管理** +- **动态数据获取** - 支持CSV、数据库、API等多种数据源 +- **分布式数据编排** - 智能的数据分发和同步 +- **测试数据生成** - 基于Faker的多样化数据生成 +- **数据提供策略** - 轮询、随机、顺序等分发策略 + +### 📈 **性能分析** +- **智能评级系统** - A-D等级自动评估 +- **多维度指标** - 响应时间、吞吐量、错误率等 +- **趋势分析** - 历史数据对比和预测 +- **可视化报告** - 丰富的图表和报告模板 + +### 🔔 **监控告警** +- **实时监控** - 系统资源和性能指标监控 +- **多级告警** - 信息、警告、严重等级别 +- **多渠道通知** - 飞书、钉钉、邮件、企业微信 +- **告警规则** - 灵活的阈值配置和触发条件 + +### 🔌 **插件生态** +- **插件架构** - 10+种插件类型支持 +- **热插拔** - 动态加载和卸载插件 +- **扩展接口** - 标准化的插件开发接口 +- **内置插件** - 丰富的开箱即用功能 + +### 🌊 **负载形状** +- **7种负载模式** - 波浪、阶梯、尖峰、自适应等 +- **自定义形状** - 支持复杂负载场景设计 +- **动态调整** - 运行时负载参数调整 +- **场景编排** - 多阶段负载组合 + +### 🔗 **平台集成** +- **第三方集成** - 支持嵌入测试平台 +- **API接口** - 完整的REST API +- **CI/CD集成** - 持续集成流水线支持 +- **云原生** - Docker和Kubernetes部署 + +## 🚀 快速开始 + +### 安装要求 + +- Python 3.7+ +- 推荐使用虚拟环境 + +### 快速安装 + +```bash +# 克隆项目 +git clone git@github.com:ljxpython/locust_framework.git +cd locust_framework + +# 创建虚拟环境 +python -m venv venv +source venv/bin/activate # Linux/Mac +# 或 venv\Scripts\activate # Windows + +# 安装依赖 +pip install -r requirements.txt +``` + +### 5分钟上手 + +1. **创建测试脚本** +```python +# locustfiles/quickstart.py +from locust import HttpUser, task +from src.utils.locust_report import manual_report + +class QuickStartUser(HttpUser): + host = "https://httpbin.org" + + @task + def test_get(self): + with manual_report("GET请求测试"): + self.client.get("/get") + + @task(2) # 权重为2 + def test_post(self): + with manual_report("POST请求测试"): + self.client.post("/post", json={"key": "value"}) +``` + +2. **启动Web UI测试** +```bash +# 使用Make命令(推荐) +make run-web script=locustfiles/quickstart.py + +# 或使用原生Locust命令 +locust -f locustfiles/quickstart.py +``` + +3. **访问Web界面** + - 打开浏览器访问 http://localhost:8089 + - 设置用户数和启动速率 + - 开始测试并查看实时结果 + +4. **无头模式测试** +```bash +# 快速执行 +make run script=locustfiles/quickstart.py u=10 r=2 t=60s + +# 完整参数 +locust -f locustfiles/quickstart.py --headless -u 10 -r 2 -t 60s --html=reports/report.html +``` + +### 📚 详细文档 + +- **[📖 完整文档](docs/README.md)** - 详细的使用指南和API参考 +- **[🚀 快速开始](docs/getting-started/quickstart.md)** - 5分钟快速上手 +- **[🏗️ 架构设计](docs/architecture/overview.md)** - 系统架构和设计理念 +- **[🔌 插件开发](docs/development/plugin-development.md)** - 插件开发完整指南 +- **[⚙️ 配置参考](docs/configuration/framework-config.md)** - 详细配置说明 +- **[💡 使用示例](docs/examples/basic-examples.md)** - 丰富的实战示例 + +## 📁 项目结构 + +``` +locust_framework/ +├── 📁 conf/ # 配置文件 +│ ├── config.py # 配置管理 +│ ├── constants.py # 常量定义 +│ └── settings.yaml # 主配置文件 +├── 📁 docs/ # 文档系统 +│ ├── getting-started/ # 入门指南 +│ ├── architecture/ # 架构文档 +│ ├── api/ # API参考 +│ ├── development/ # 开发指南 +│ ├── examples/ # 使用示例 +│ └── configuration/ # 配置参考 +├── 📁 locustfiles/ # 测试脚本 +│ ├── demo.py # 示例脚本 +│ ├── shape_classes/ # 负载形状类 +│ └── test_*/ # 测试场景目录 +├── 📁 src/ # 核心源码 +│ ├── analysis/ # 性能分析模块 +│ ├── monitoring/ # 监控告警模块 +│ ├── data_manager/ # 数据管理模块 +│ ├── plugins/ # 插件系统 +│ ├── notifications/ # 通知服务 +│ ├── load_shapes/ # 负载形状 +│ ├── client/ # 客户端封装 +│ ├── model/ # 数据模型 +│ └── utils/ # 工具模块 +├── 📁 reports/ # 测试报告 +├── 📁 logs/ # 日志文件 +├── main.py # 主程序入口 +├── requirements.txt # 依赖列表 +└── README.md # 项目说明 +``` + +## 🎯 主要功能详解 + +### 🔧 自定义参数系统 + +框架支持灵活的参数传递和配置管理,让测试更加可控和可配置。 + +**核心特性:** +- ✅ 支持指定轮数`loop_num`后自动停止压测 +- ✅ 支持使用集合点且自定义集合数`rendezvous_num` +- ✅ 支持其他业务相关参数通过Web/终端传入 +- ✅ 环境变量和配置文件支持 + +### 📊 智能性能分析 + +基于多维度指标的智能分析系统,提供专业的性能评估。 + +**分析维度:** +- 📈 **响应时间分析** - P50/P90/P95/P99百分位数 +- 🚀 **吞吐量分析** - TPS、并发处理能力 +- ❌ **错误率分析** - 错误分布和根因分析 +- 📊 **稳定性分析** - 性能波动和趋势预测 +- 🎯 **综合评级** - A-D等级自动评估 + +### 🔔 多渠道告警通知 + +实时监控和智能告警,确保问题及时发现和处理。 + +**支持渠道:** +- 🚀 **飞书** - 支持富文本消息和@功能 +- 📱 **钉钉** - 群机器人消息推送 +- 📧 **邮件** - SMTP邮件通知 +- 💬 **企业微信** - 企业内部消息通知 + +### 🌊 多样化负载形状 + +支持7种内置负载模式,满足不同测试场景需求。 + +**负载模式:** +- 📈 **波浪形** - 周期性负载变化 +- 🏗️ **阶梯形** - 逐步递增负载 +- ⚡ **尖峰形** - 突发流量测试 +- 🎯 **自适应** - 基于响应时间自动调整 +- 📊 **自定义** - 灵活的负载场景编排 + +### 🔌 插件化架构 + +强大的插件系统,支持功能扩展和定制化开发。 + +**插件类型:** +- 🧪 **Locust插件** - 扩展Locust核心功能 +- 📊 **报告插件** - 自定义报告格式和内容 +- 📡 **监控插件** - 系统和应用监控 +- 💾 **数据插件** - 数据源和存储扩展 +- 🔔 **通知插件** - 消息通知渠道扩展 + +### 🎯 分布式测试支持 + +天然支持分布式部署,轻松应对大规模性能测试需求。 + +**分布式特性:** +- 🏗️ **Master-Worker架构** - 支持多节点协同 +- 📊 **数据同步** - 智能的测试数据分发 +- 📈 **负载均衡** - 自动负载分配和调度 +- 🔄 **故障恢复** - 节点故障自动处理 + +--- + +## 📋 原有功能说明 + +> 基于locust框架进行二次开发: https://locust.io/ > > 主要封装完善功能如下: > @@ -18,9 +253,257 @@ -## 框架结构 +--- + +## 📖 使用指南 + +### 🎯 测试执行方式 + +框架支持多种测试执行方式,满足不同场景需求: + +#### 1. Web UI模式(推荐新手) -文件结构如下: +```bash +# 使用Make命令启动 +make run-web script=locustfiles/demo.py + +# 或使用原生Locust命令 +locust -f locustfiles/demo.py +``` + +**特点:** +- 🌐 友好的Web界面 +- 📊 实时监控和图表 +- ⚙️ 可视化参数配置 +- 📈 动态负载调整 + +#### 2. 无头模式(适合CI/CD) + +```bash +# 快速执行 +make run script=locustfiles/demo.py u=10 r=2 t=60s + +# 完整参数执行 +locust -f locustfiles/demo.py --headless \ + --users 10 --spawn-rate 2 --run-time 60s \ + --html=reports/report.html \ + --csv=reports/results +``` + +**特点:** +- 🚀 自动化执行 +- 📋 命令行参数控制 +- 📊 自动生成报告 +- 🔄 适合持续集成 + +#### 3. 分布式模式(大规模测试) + +```bash +# 启动Master节点 +locust -f locustfiles/demo.py --master + +# 启动Worker节点 +locust -f locustfiles/demo.py --worker --master-host= + +# 多进程Worker +locust -f locustfiles/demo.py --worker --processes 4 +``` + +**特点:** +- 🏗️ 多节点协同 +- 📈 线性扩展能力 +- 🔄 故障自动恢复 +- 📊 统一结果收集 + +### 🔧 自定义参数配置 + +框架提供强大的参数化能力,支持灵活的测试配置: + +#### 参数定义示例 + +```python +@events.init_command_line_parser.add_listener +def _(parser): + parser.add_argument("--product_id", type=str, default="1925511528732168192", help="测试业务ID") + parser.add_argument("--loop_num", type=int, default=1, help="执行轮数") + parser.add_argument("--is_rendezvous", type=str_to_bool, default=False, help="是否使用集合点") + parser.add_argument("--rendezvous_num", type=int, default=1, help="集合点数量") +``` + +#### 参数使用示例 + +```python +class WebsiteUser(HttpUser): + @task + def my_task(self): + # 获取自定义参数 + loop_num = self.environment.parsed_options.loop_num + product_id = self.environment.parsed_options.product_id + + # 使用集合点 + if self.environment.parsed_options.is_rendezvous: + with self.environment.shared: + # 并发执行的业务逻辑 + self.client.get(f"/api/product/{product_id}") +``` + +### 📊 性能分析和报告 + +框架提供智能的性能分析和丰富的报告功能: + +#### 自动性能分析 + +- **响应时间分析** - P50/P90/P95/P99百分位数 +- **吞吐量分析** - TPS和并发处理能力 +- **错误率分析** - 错误分布和根因分析 +- **综合评级** - A-D等级自动评估 + +#### 多格式报告 + +- **HTML报告** - 交互式图表和详细分析 +- **CSV数据** - 原始数据导出和二次分析 +- **JSON格式** - API集成和自动化处理 +- **实时监控** - Web界面实时数据展示 + +### 🔔 监控告警配置 + +#### 告警规则配置 + +```yaml +# conf/settings.yaml +monitoring: + alerts: + response_time: + warning: 1000 # 1秒警告 + critical: 3000 # 3秒严重 + error_rate: + warning: 5 # 5%警告 + critical: 10 # 10%严重 + throughput: + warning: 100 # 低于100 TPS警告 +``` + +#### 通知渠道配置 + +```yaml +notifications: + feishu: + webhook_url: "https://open.feishu.cn/open-apis/bot/v2/hook/xxx" + enabled: true + dingtalk: + webhook_url: "https://oapi.dingtalk.com/robot/send?access_token=xxx" + enabled: true + email: + smtp_server: "smtp.gmail.com" + smtp_port: 587 + enabled: true +``` + +### 🌊 负载形状使用 + +框架支持多种负载模式,模拟真实的用户行为: + +#### 使用负载形状 + +```python +# 在测试脚本中继承负载形状类 +from locustfiles.shape_classes.wave_shape import WaveLoadShape + +class MyTestUser(HttpUser): + # 测试逻辑 + pass + +# 使用命令行选择负载形状 +locust -f test.py --class-picker WaveLoadShape +``` + +#### 可用负载模式 + +- **WaveLoadShape** - 波浪形负载 +- **SpikeLoadShape** - 尖峰负载 +- **StairStepLoadShape** - 阶梯负载 +- **AdaptiveLoadShape** - 自适应负载 + +### 🔌 插件使用 + +#### 启用插件 + +```yaml +# conf/settings.yaml +plugins: + enabled: + - performance_analyzer + - system_monitor + - notification_service + - data_manager +``` + +#### 自定义插件 + +```python +from src.plugins.base import BasePlugin + +class MyCustomPlugin(BasePlugin): + def on_test_start(self, environment, **kwargs): + # 测试开始时的逻辑 + pass + + def on_test_stop(self, environment, **kwargs): + # 测试结束时的逻辑 + pass +``` + +--- + +## 📋 详细功能说明 + +### 🔧 自定义参数详细说明 + +框架支持灵活的参数传递和配置管理: + +1. **封装测试框架** - 支持指定轮数`loop_num`后自动停止压测 +2. **集合点功能** - 支持使用集合点且自定义集合数`rendezvous_num` +3. **业务参数** - 支持其他业务相关参数通过web/终端传入 + +### 📊 智能性能分析 + +基于多维度指标的智能分析系统: + +- **响应时间分析** - P50/P90/P95/P99百分位数统计 +- **吞吐量分析** - TPS和并发处理能力评估 +- **错误率分析** - 错误分布和根因分析 +- **稳定性分析** - 性能波动和趋势预测 +- **综合评级** - A-D等级自动评估系统 + +### 🔔 多渠道告警通知 + +实时监控和智能告警系统: + +- **飞书通知** - 支持富文本消息和@功能 +- **钉钉通知** - 群机器人消息推送 +- **邮件通知** - SMTP邮件告警 +- **企业微信** - 企业内部消息通知 + +### 🌊 多样化负载形状 + +支持7种内置负载模式: + +- **波浪形负载** - 周期性负载变化模拟 +- **阶梯形负载** - 逐步递增负载测试 +- **尖峰形负载** - 突发流量压力测试 +- **自适应负载** - 基于响应时间自动调整 +- **自定义负载** - 灵活的负载场景编排 + +### 🎯 分布式测试支持 + +天然支持分布式部署: + +- **Master-Worker架构** - 支持多节点协同测试 +- **数据同步机制** - 智能的测试数据分发 +- **负载均衡** - 自动负载分配和调度 +- **故障恢复** - 节点故障自动处理机制 + +--- ```bash . ├── README.md @@ -419,9 +902,66 @@ class WebsiteUser(HttpUser): +--- + +## 🤝 贡献指南 + +欢迎所有形式的贡献!无论是bug报告、功能请求、代码贡献还是文档改进。 + +### 如何贡献 + +1. **Fork项目** - 点击右上角的Fork按钮 +2. **创建分支** - `git checkout -b feature/your-feature-name` +3. **提交更改** - `git commit -am 'Add some feature'` +4. **推送分支** - `git push origin feature/your-feature-name` +5. **创建PR** - 在GitHub上创建Pull Request + +### 贡献类型 + +- 🐛 **Bug修复** - 发现并修复问题 +- ✨ **新功能** - 添加新的功能特性 +- 📚 **文档改进** - 完善文档和示例 +- 🧪 **测试用例** - 增加测试覆盖率 +- 🔧 **工具优化** - 改进开发工具和流程 + +### 开发规范 + +- 遵循[编码规范](docs/development/coding-standards.md) +- 添加适当的测试用例 +- 更新相关文档 +- 确保CI/CD流程通过 + +## 📄 许可证 + +本项目采用 [MIT License](LICENSE) 开源协议。 + +## 🌟 致谢 + +感谢所有为这个项目做出贡献的开发者和用户! + +特别感谢: +- [Locust](https://locust.io/) - 优秀的负载测试框架 +- 所有提供反馈和建议的用户 +- 开源社区的支持和贡献 + +## 📞 联系我 + +### 技术支持 + +### 作者联系方式 + 也欢迎和您交流讨论😊 -个人微信号 +**个人微信号** + +个人微信二维码 + + + +--- + +**⭐ 如果这个项目对您有帮助,请给我一个Star!** +**🔗 项目链接**: [GitHub Repository](https://github.com/ljxpython/locust_framework) -Description +**📖 在线文档**: [完整文档](docs/README.md) diff --git a/docs/.DS_Store b/docs/.DS_Store index c30c60ed6f43bc2a42bc222829709bbad89acfbc..0496b98b1e7734a7cbb88ba16cd4a5b6c7a3a4ca 100644 GIT binary patch delta 300 zcmYk2Jqp4=6okjbpM@w9T+(Qvy+XW&g_l^9Xaf1eM6t8+0ES$`R?>PeTW3Wi3lBbK zhS|rvxi|Oj7#&e5rzZdq=v*5Gg%!z5`NJuHlZ)E%1MNqr&&xC$VF4_e)+XFW1 v?v%VjLDmcE)%<_7T27y#)306GZ7g94-r}j=W^KzwPv2o)ujQoK$d~y6^#wre delta 111 zcmZoMXfc=|#>AjHu~68Qk%57Mg`t2UlOdU*IHfo_Cn-Na2Pg&tOc06zqy~ru7+~_7 sKQfB3ZDN|pxS5@Up95$TP~bcBWPTAvPLSFJkiN+#Jj$D6L{=~Z0MEA;mjD0& diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..b823c86 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,105 @@ +# Locust性能测试框架文档 + +欢迎使用Locust性能测试框架!本文档将帮助您快速上手并深入了解框架的各项功能。 + +## 📚 文档导航 + +### 🚀 快速开始 +- [安装指南](getting-started/installation.md) - 环境搭建和依赖安装 +- [快速入门](getting-started/quickstart.md) - 5分钟上手指南 +- [基础概念](getting-started/concepts.md) - 核心概念和术语 +- [第一个测试](getting-started/first-test.md) - 创建您的第一个性能测试 + +### 🏗️ 架构设计 +- [整体架构](architecture/overview.md) - 框架整体架构设计 +- [模块设计](architecture/modules.md) - 各模块详细设计 +- [插件系统](architecture/plugin-system.md) - 插件架构和扩展机制 +- [数据流](architecture/data-flow.md) - 数据处理和流转机制 + +### 📖 API参考 +- [性能分析API](api/analysis.md) - 性能分析模块API +- [监控告警API](api/monitoring.md) - 监控告警模块API +- [数据管理API](api/data-manager.md) - 数据管理模块API +- [插件接口API](api/plugins.md) - 插件开发接口 +- [负载模式API](api/load-shapes.md) - 负载模式API +- [工具类API](api/utilities.md) - 工具类和辅助函数 + +### 💻 开发指南 +- [开发环境搭建](development/setup.md) - 开发环境配置 +- [代码规范](development/coding-standards.md) - 编码规范和最佳实践 +- [插件开发](development/plugin-development.md) - 自定义插件开发指南 +- [负载模式开发](development/load-shape-development.md) - 自定义负载模式开发 +- [贡献指南](development/contributing.md) - 如何为项目贡献代码 +- [测试指南](development/testing.md) - 单元测试和集成测试 + +### 📋 示例和最佳实践 +- [基础示例](examples/basic-examples.md) - 基础功能使用示例 +- [高级示例](examples/advanced-examples.md) - 高级功能和复杂场景 +- [性能测试最佳实践](examples/best-practices.md) - 性能测试经验和技巧 +- [常见问题解答](examples/faq.md) - 常见问题和解决方案 +- [故障排除](examples/troubleshooting.md) - 问题诊断和解决 + +## 🔧 配置参考 + +### 核心配置 +- [框架配置](configuration/framework-config.md) - 框架核心配置选项 +- [插件配置](configuration/plugin-config.md) - 插件系统配置 +- [监控配置](configuration/monitoring-config.md) - 监控告警配置 +- [通知配置](configuration/notification-config.md) - 通知渠道配置 + +### 环境配置 +- [开发环境](configuration/development.md) - 开发环境配置 +- [测试环境](configuration/testing.md) - 测试环境配置 +- [生产环境](configuration/production.md) - 生产环境配置 +- [分布式配置](configuration/distributed.md) - 分布式测试配置 + +## 📊 功能特性 + +### 核心功能 +- **性能分析**: 全面的性能指标分析和评分系统 +- **实时监控**: 可配置的实时监控和告警机制 +- **数据管理**: 智能的测试数据生成和管理 +- **插件系统**: 灵活的插件化扩展架构 +- **负载模式**: 丰富的负载模式支持 +- **多格式报告**: HTML、JSON、CSV、Markdown报告 + +### 高级特性 +- **趋势分析**: 历史数据趋势分析和预测 +- **自适应负载**: 基于性能指标的自适应负载调整 +- **分布式同步**: 分布式环境下的数据同步 +- **多渠道通知**: 支持飞书、钉钉、邮件等多种通知方式 +- **插件热加载**: 运行时动态加载和卸载插件 + +## 🎯 使用场景 + +### 性能测试类型 +- **负载测试**: 验证系统在预期负载下的性能表现 +- **压力测试**: 确定系统的性能极限和瓶颈 +- **容量测试**: 评估系统的最大处理能力 +- **稳定性测试**: 长时间运行下的系统稳定性 +- **峰值测试**: 突发流量下的系统响应能力 + +### 应用领域 +- **Web应用**: HTTP/HTTPS接口性能测试 +- **API服务**: RESTful API和GraphQL性能测试 +- **微服务**: 分布式微服务架构性能测试 +- **数据库**: 数据库连接和查询性能测试 +- **消息队列**: 消息中间件性能测试 + +## 🚀 快速链接 + +- [立即开始](getting-started/quickstart.md) - 快速上手指南 +- [示例代码](examples/basic-examples.md) - 查看示例代码 +- [API文档](api/analysis.md) - 浏览API文档 +- [插件开发](development/plugin-development.md) - 开发自定义插件 +- [最佳实践](examples/best-practices.md) - 学习最佳实践 + +## 🔄 版本信息 + +- **当前版本**: v2.0.0 +- **更新日期**: 2025-01-01 +- **兼容性**: Python 3.7+, Locust 2.0+ + +--- + +*本文档持续更新中,如有疑问请随时联系我们。* diff --git a/docs/api/analysis.md b/docs/api/analysis.md new file mode 100644 index 0000000..623bea1 --- /dev/null +++ b/docs/api/analysis.md @@ -0,0 +1,550 @@ +# 性能分析API参考 + +本文档详细介绍性能分析模块的API接口,包括性能分析器、趋势分析器和报告生成器。 + +## 📊 PerformanceAnalyzer + +性能分析器是框架的核心分析组件,提供全面的性能指标分析和评分功能。 + +### 类定义 + +```python +from src.analysis.performance_analyzer import PerformanceAnalyzer + +class PerformanceAnalyzer: + """性能分析器 + + 提供全面的性能分析功能,包括响应时间分析、吞吐量分析、 + 错误率分析和综合性能评分。 + """ +``` + +### 构造函数 + +```python +def __init__(self, config: Optional[Dict[str, Any]] = None): + """初始化性能分析器 + + Args: + config: 配置参数字典,可选 + - response_time_thresholds: 响应时间阈值配置 + - throughput_thresholds: 吞吐量阈值配置 + - error_rate_thresholds: 错误率阈值配置 + - weights: 各指标权重配置 + + Example: + >>> config = { + ... 'response_time_thresholds': { + ... 'excellent': 500, + ... 'good': 1000, + ... 'acceptable': 2000, + ... 'poor': 5000 + ... } + ... } + >>> analyzer = PerformanceAnalyzer(config) + """ +``` + +### 核心方法 + +#### comprehensive_analysis() + +```python +def comprehensive_analysis(self, test_data: Dict[str, Any]) -> Dict[str, Any]: + """执行综合性能分析 + + Args: + test_data: 测试数据字典,包含以下字段: + - test_name: 测试名称 + - start_time: 开始时间 + - end_time: 结束时间 + - duration: 测试持续时间(秒) + - users: 用户数 + - requests: 请求列表,每个请求包含: + - response_time: 响应时间(毫秒) + - success: 是否成功(布尔值) + - timestamp: 时间戳 + - name: 请求名称(可选) + - method: HTTP方法(可选) + + Returns: + Dict[str, Any]: 分析结果字典,包含: + - test_info: 测试基本信息 + - response_time: 响应时间分析结果 + - throughput: 吞吐量分析结果 + - error_analysis: 错误分析结果 + - performance_grade: 性能评分结果 + - overall_grade: 总体评级 + - recommendations: 优化建议 + + Example: + >>> test_data = { + ... 'test_name': '用户登录测试', + ... 'start_time': '2024-01-01 10:00:00', + ... 'end_time': '2024-01-01 10:30:00', + ... 'duration': 1800, + ... 'users': 100, + ... 'requests': [ + ... {'response_time': 500, 'success': True, 'timestamp': '2024-01-01 10:00:01'}, + ... {'response_time': 800, 'success': True, 'timestamp': '2024-01-01 10:00:02'}, + ... # ... 更多请求数据 + ... ] + ... } + >>> result = analyzer.comprehensive_analysis(test_data) + >>> print(f"总体评级: {result['overall_grade']}") + """ +``` + +#### analyze_response_time() + +```python +def analyze_response_time(self, requests: List[Dict[str, Any]]) -> Dict[str, Any]: + """分析响应时间 + + Args: + requests: 请求列表 + + Returns: + Dict[str, Any]: 响应时间分析结果 + - avg: 平均响应时间 + - min: 最小响应时间 + - max: 最大响应时间 + - median: 中位数响应时间 + - p50, p90, p95, p99: 百分位数响应时间 + - std_dev: 标准差 + - distribution: 响应时间分布 + - grade: 响应时间评级 + + Example: + >>> requests = [ + ... {'response_time': 500, 'success': True}, + ... {'response_time': 800, 'success': True}, + ... {'response_time': 1200, 'success': False} + ... ] + >>> result = analyzer.analyze_response_time(requests) + >>> print(f"P95响应时间: {result['p95']}ms") + """ +``` + +#### analyze_throughput() + +```python +def analyze_throughput(self, requests: List[Dict[str, Any]], + duration: float) -> Dict[str, Any]: + """分析吞吐量 + + Args: + requests: 请求列表 + duration: 测试持续时间(秒) + + Returns: + Dict[str, Any]: 吞吐量分析结果 + - total_requests: 总请求数 + - successful_requests: 成功请求数 + - failed_requests: 失败请求数 + - avg_tps: 平均TPS + - peak_tps: 峰值TPS + - min_tps: 最低TPS + - tps_trend: TPS趋势数据 + - grade: 吞吐量评级 + + Example: + >>> result = analyzer.analyze_throughput(requests, 1800) + >>> print(f"平均TPS: {result['avg_tps']}") + """ +``` + +#### analyze_errors() + +```python +def analyze_errors(self, requests: List[Dict[str, Any]]) -> Dict[str, Any]: + """分析错误情况 + + Args: + requests: 请求列表 + + Returns: + Dict[str, Any]: 错误分析结果 + - total_errors: 总错误数 + - error_rate: 错误率(百分比) + - error_types: 错误类型分布 + - error_trend: 错误趋势 + - critical_errors: 严重错误列表 + - grade: 错误率评级 + + Example: + >>> result = analyzer.analyze_errors(requests) + >>> print(f"错误率: {result['error_rate']:.2f}%") + """ +``` + +#### calculate_performance_grade() + +```python +def calculate_performance_grade(self, response_time_result: Dict[str, Any], + throughput_result: Dict[str, Any], + error_result: Dict[str, Any]) -> Dict[str, Any]: + """计算性能评分 + + Args: + response_time_result: 响应时间分析结果 + throughput_result: 吞吐量分析结果 + error_result: 错误分析结果 + + Returns: + Dict[str, Any]: 性能评分结果 + - response_time_score: 响应时间得分 + - throughput_score: 吞吐量得分 + - error_rate_score: 错误率得分 + - stability_score: 稳定性得分 + - overall_score: 总体得分 + - grade: 评级(A/B/C/D) + - grade_description: 评级描述 + + Example: + >>> grade_result = analyzer.calculate_performance_grade( + ... response_time_result, throughput_result, error_result + ... ) + >>> print(f"总体评级: {grade_result['grade']}") + """ +``` + +### 配置选项 + +```python +# 默认配置 +DEFAULT_CONFIG = { + 'response_time_thresholds': { + 'excellent': 500, # A级: < 500ms + 'good': 1000, # B级: < 1000ms + 'acceptable': 2000, # C级: < 2000ms + 'poor': 5000 # D级: >= 2000ms + }, + 'throughput_thresholds': { + 'excellent': 1000, # A级: > 1000 TPS + 'good': 500, # B级: > 500 TPS + 'acceptable': 100, # C级: > 100 TPS + 'poor': 50 # D级: <= 100 TPS + }, + 'error_rate_thresholds': { + 'excellent': 0.1, # A级: < 0.1% + 'good': 1.0, # B级: < 1% + 'acceptable': 5.0, # C级: < 5% + 'poor': 10.0 # D级: >= 5% + }, + 'weights': { + 'response_time': 0.4, # 响应时间权重40% + 'throughput': 0.3, # 吞吐量权重30% + 'error_rate': 0.2, # 错误率权重20% + 'stability': 0.1 # 稳定性权重10% + } +} +``` + +## 📈 TrendAnalyzer + +趋势分析器提供历史数据分析和性能趋势预测功能。 + +### 类定义 + +```python +from src.analysis.trend_analyzer import TrendAnalyzer + +class TrendAnalyzer: + """趋势分析器 + + 提供历史性能数据的趋势分析和预测功能。 + """ +``` + +### 核心方法 + +#### analyze_historical_trend() + +```python +def analyze_historical_trend(self, historical_data: List[Dict[str, Any]]) -> Dict[str, Any]: + """分析历史趋势 + + Args: + historical_data: 历史测试数据列表,每个元素包含: + - timestamp: 时间戳 + - avg_response_time: 平均响应时间 + - throughput: 吞吐量 + - error_rate: 错误率 + - users: 用户数 + + Returns: + Dict[str, Any]: 趋势分析结果 + - response_time_trend: 响应时间趋势 + - throughput_trend: 吞吐量趋势 + - error_rate_trend: 错误率趋势 + - performance_trend: 整体性能趋势 + - trend_summary: 趋势摘要 + + Example: + >>> historical_data = [ + ... { + ... 'timestamp': '2024-01-01', + ... 'avg_response_time': 500, + ... 'throughput': 100, + ... 'error_rate': 1.0, + ... 'users': 50 + ... }, + ... # ... 更多历史数据 + ... ] + >>> trend_result = trend_analyzer.analyze_historical_trend(historical_data) + """ +``` + +#### predict_performance() + +```python +def predict_performance(self, historical_data: List[Dict[str, Any]], + prediction_days: int = 7) -> Dict[str, Any]: + """性能预测 + + Args: + historical_data: 历史数据 + prediction_days: 预测天数 + + Returns: + Dict[str, Any]: 预测结果 + - predicted_response_time: 预测响应时间 + - predicted_throughput: 预测吞吐量 + - predicted_error_rate: 预测错误率 + - confidence_interval: 置信区间 + - prediction_accuracy: 预测准确度 + + Example: + >>> prediction = trend_analyzer.predict_performance(historical_data, 7) + >>> print(f"预测响应时间: {prediction['predicted_response_time']}") + """ +``` + +## 📋 ReportGenerator + +报告生成器提供多格式的测试报告生成功能。 + +### 类定义 + +```python +from src.analysis.report_generator import ReportGenerator + +class ReportGenerator: + """报告生成器 + + 支持生成HTML、JSON、CSV、Markdown等多种格式的测试报告。 + """ +``` + +### 核心方法 + +#### generate_html_report() + +```python +def generate_html_report(self, analysis_result: Dict[str, Any], + output_path: str, + template_name: str = 'default') -> bool: + """生成HTML报告 + + Args: + analysis_result: 分析结果数据 + output_path: 输出文件路径 + template_name: 模板名称,默认为'default' + + Returns: + bool: 生成是否成功 + + Example: + >>> success = report_generator.generate_html_report( + ... analysis_result, + ... 'reports/test_report.html' + ... ) + >>> print(f"报告生成{'成功' if success else '失败'}") + """ +``` + +#### generate_json_report() + +```python +def generate_json_report(self, analysis_result: Dict[str, Any], + output_path: str) -> bool: + """生成JSON报告 + + Args: + analysis_result: 分析结果数据 + output_path: 输出文件路径 + + Returns: + bool: 生成是否成功 + + Example: + >>> success = report_generator.generate_json_report( + ... analysis_result, + ... 'reports/test_report.json' + ... ) + """ +``` + +#### generate_csv_report() + +```python +def generate_csv_report(self, analysis_result: Dict[str, Any], + output_path: str) -> bool: + """生成CSV报告 + + Args: + analysis_result: 分析结果数据 + output_path: 输出文件路径 + + Returns: + bool: 生成是否成功 + + Example: + >>> success = report_generator.generate_csv_report( + ... analysis_result, + ... 'reports/test_report.csv' + ... ) + """ +``` + +#### generate_markdown_report() + +```python +def generate_markdown_report(self, analysis_result: Dict[str, Any], + output_path: str) -> bool: + """生成Markdown报告 + + Args: + analysis_result: 分析结果数据 + output_path: 输出文件路径 + + Returns: + bool: 生成是否成功 + + Example: + >>> success = report_generator.generate_markdown_report( + ... analysis_result, + ... 'reports/test_report.md' + ... ) + """ +``` + +## 🔧 工具函数 + +### 指标计算工具 + +```python +from src.analysis.metrics_calculator import MetricsCalculator + +class MetricsCalculator: + """指标计算工具类""" + + @staticmethod + def calculate_percentiles(values: List[float], + percentiles: List[int] = [50, 90, 95, 99]) -> Dict[str, float]: + """计算百分位数 + + Args: + values: 数值列表 + percentiles: 百分位数列表 + + Returns: + Dict[str, float]: 百分位数结果 + """ + + @staticmethod + def calculate_statistics(values: List[float]) -> Dict[str, float]: + """计算基础统计指标 + + Args: + values: 数值列表 + + Returns: + Dict[str, float]: 统计结果 + - mean: 平均值 + - median: 中位数 + - std_dev: 标准差 + - variance: 方差 + - min: 最小值 + - max: 最大值 + """ +``` + +## 📊 使用示例 + +### 完整分析流程 + +```python +from src.analysis.performance_analyzer import PerformanceAnalyzer +from src.analysis.trend_analyzer import TrendAnalyzer +from src.analysis.report_generator import ReportGenerator + +# 1. 创建分析器 +analyzer = PerformanceAnalyzer() +trend_analyzer = TrendAnalyzer() +report_generator = ReportGenerator() + +# 2. 准备测试数据 +test_data = { + 'test_name': 'API性能测试', + 'start_time': '2024-01-01 10:00:00', + 'end_time': '2024-01-01 10:30:00', + 'duration': 1800, + 'users': 100, + 'requests': [ + # ... 请求数据 + ] +} + +# 3. 执行性能分析 +analysis_result = analyzer.comprehensive_analysis(test_data) + +# 4. 趋势分析(如果有历史数据) +if historical_data: + trend_result = trend_analyzer.analyze_historical_trend(historical_data) + analysis_result['trend_analysis'] = trend_result + +# 5. 生成报告 +report_generator.generate_html_report(analysis_result, 'reports/performance_report.html') +report_generator.generate_json_report(analysis_result, 'reports/performance_report.json') + +print(f"性能分析完成,总体评级: {analysis_result['overall_grade']}") +``` + +### 自定义配置示例 + +```python +# 自定义分析配置 +custom_config = { + 'response_time_thresholds': { + 'excellent': 300, # 更严格的响应时间要求 + 'good': 800, + 'acceptable': 1500, + 'poor': 3000 + }, + 'weights': { + 'response_time': 0.5, # 更重视响应时间 + 'throughput': 0.2, + 'error_rate': 0.2, + 'stability': 0.1 + } +} + +analyzer = PerformanceAnalyzer(custom_config) +``` + +## ⚠️ 注意事项 + +1. **数据格式**: 确保输入数据格式正确,特别是时间戳格式 +2. **内存使用**: 大量数据分析时注意内存使用情况 +3. **并发安全**: 分析器实例不是线程安全的,多线程使用时需要注意 +4. **配置验证**: 自定义配置时确保参数合理性 +5. **异常处理**: 建议在调用API时添加适当的异常处理 + +## 🔗 相关文档 + +- [监控告警API](monitoring.md) - 监控告警模块API +- [数据管理API](data-manager.md) - 数据管理模块API +- [插件接口API](plugins.md) - 插件开发接口 +- [配置参考](../configuration/framework-config.md) - 详细配置说明 diff --git a/docs/api/data-manager.md b/docs/api/data-manager.md new file mode 100644 index 0000000..9f3ea47 --- /dev/null +++ b/docs/api/data-manager.md @@ -0,0 +1,659 @@ +# 数据管理API参考 + +本文档详细介绍数据管理模块的API接口,包括数据生成器、数据提供者和数据分发器。 + +## 📊 DataGenerator + +数据生成器提供多种类型的测试数据生成功能。 + +### 类定义 + +```python +from src.data_manager.data_generator import DataGenerator + +class DataGenerator: + """测试数据生成器 + + 基于Faker库提供丰富的测试数据生成功能, + 支持多种数据类型和本地化设置。 + """ +``` + +### 构造函数 + +```python +def __init__(self, locale: str = "zh_CN", seed: Optional[int] = None): + """初始化数据生成器 + + Args: + locale: 本地化设置,默认中文 + seed: 随机种子,用于生成可重复的数据 + + Example: + >>> generator = DataGenerator(locale="en_US", seed=12345) + >>> generator_cn = DataGenerator() # 使用中文 + """ +``` + +### 基础数据生成 + +#### generate_name() + +```python +def generate_name(self, gender: Optional[str] = None) -> str: + """生成姓名 + + Args: + gender: 性别 ("male"/"female"),可选 + + Returns: + str: 生成的姓名 + + Example: + >>> generator.generate_name() + '张三' + >>> generator.generate_name(gender="female") + '李小红' + """ +``` + +#### generate_email() + +```python +def generate_email(self, domain: Optional[str] = None) -> str: + """生成邮箱地址 + + Args: + domain: 邮箱域名,可选 + + Returns: + str: 生成的邮箱地址 + + Example: + >>> generator.generate_email() + 'zhangsan@example.com' + >>> generator.generate_email(domain="test.com") + 'user123@test.com' + """ +``` + +#### generate_phone_number() + +```python +def generate_phone_number(self, region: str = "CN") -> str: + """生成手机号码 + + Args: + region: 地区代码,默认中国 + + Returns: + str: 生成的手机号码 + + Example: + >>> generator.generate_phone_number() + '13812345678' + >>> generator.generate_phone_number(region="US") + '+1-555-123-4567' + """ +``` + +#### generate_address() + +```python +def generate_address(self, include_country: bool = False) -> Dict[str, str]: + """生成地址信息 + + Args: + include_country: 是否包含国家信息 + + Returns: + Dict[str, str]: 地址信息字典 + + Example: + >>> generator.generate_address() + { + 'province': '北京市', + 'city': '北京市', + 'district': '朝阳区', + 'street': '建国路123号', + 'postal_code': '100000' + } + """ +``` + +### 数值数据生成 + +#### generate_integer() + +```python +def generate_integer(self, min_value: int = 0, max_value: int = 100) -> int: + """生成整数 + + Args: + min_value: 最小值 + max_value: 最大值 + + Returns: + int: 生成的整数 + + Example: + >>> generator.generate_integer(1, 10) + 7 + >>> generator.generate_integer(100, 1000) + 456 + """ +``` + +#### generate_float() + +```python +def generate_float(self, min_value: float = 0.0, max_value: float = 100.0, + precision: int = 2) -> float: + """生成浮点数 + + Args: + min_value: 最小值 + max_value: 最大值 + precision: 小数位数 + + Returns: + float: 生成的浮点数 + + Example: + >>> generator.generate_float(0, 100, 2) + 45.67 + >>> generator.generate_float(1.0, 10.0, 3) + 7.234 + """ +``` + +#### generate_price() + +```python +def generate_price(self, min_price: float = 1.0, max_price: float = 1000.0, + currency: str = "CNY") -> Dict[str, Any]: + """生成价格信息 + + Args: + min_price: 最小价格 + max_price: 最大价格 + currency: 货币类型 + + Returns: + Dict[str, Any]: 价格信息 + + Example: + >>> generator.generate_price(10, 100) + { + 'amount': 45.99, + 'currency': 'CNY', + 'formatted': '¥45.99' + } + """ +``` + +### 文本数据生成 + +#### generate_text() + +```python +def generate_text(self, min_length: int = 10, max_length: int = 100, + text_type: str = "sentence") -> str: + """生成文本内容 + + Args: + min_length: 最小长度 + max_length: 最大长度 + text_type: 文本类型 ("word"/"sentence"/"paragraph") + + Returns: + str: 生成的文本 + + Example: + >>> generator.generate_text(10, 50, "sentence") + '这是一个测试句子,用于演示文本生成功能。' + >>> generator.generate_text(5, 20, "word") + '测试 数据 生成 工具' + """ +``` + +#### generate_username() + +```python +def generate_username(self, min_length: int = 6, max_length: int = 20) -> str: + """生成用户名 + + Args: + min_length: 最小长度 + max_length: 最大长度 + + Returns: + str: 生成的用户名 + + Example: + >>> generator.generate_username() + 'user_zhang123' + >>> generator.generate_username(8, 15) + 'testuser456' + """ +``` + +### 日期时间生成 + +#### generate_datetime() + +```python +def generate_datetime(self, start_date: Optional[str] = None, + end_date: Optional[str] = None, + format_str: str = "%Y-%m-%d %H:%M:%S") -> str: + """生成日期时间 + + Args: + start_date: 开始日期 (YYYY-MM-DD) + end_date: 结束日期 (YYYY-MM-DD) + format_str: 格式字符串 + + Returns: + str: 生成的日期时间字符串 + + Example: + >>> generator.generate_datetime() + '2023-12-01 14:30:45' + >>> generator.generate_datetime("2023-01-01", "2023-12-31") + '2023-06-15 09:22:33' + """ +``` + +#### generate_timestamp() + +```python +def generate_timestamp(self, start_timestamp: Optional[int] = None, + end_timestamp: Optional[int] = None) -> int: + """生成时间戳 + + Args: + start_timestamp: 开始时间戳 + end_timestamp: 结束时间戳 + + Returns: + int: 生成的时间戳 + + Example: + >>> generator.generate_timestamp() + 1701234567 + """ +``` + +### 业务数据生成 + +#### generate_user_profile() + +```python +def generate_user_profile(self, include_avatar: bool = True) -> Dict[str, Any]: + """生成用户资料 + + Args: + include_avatar: 是否包含头像URL + + Returns: + Dict[str, Any]: 用户资料信息 + + Example: + >>> generator.generate_user_profile() + { + 'id': 'user_123456', + 'username': 'zhangsan123', + 'name': '张三', + 'email': 'zhangsan@example.com', + 'phone': '13812345678', + 'age': 28, + 'gender': 'male', + 'avatar': 'https://example.com/avatar/123.jpg', + 'bio': '这是一个测试用户的简介。', + 'created_at': '2023-01-15 10:30:00' + } + """ +``` + +#### generate_product_info() + +```python +def generate_product_info(self, category: Optional[str] = None) -> Dict[str, Any]: + """生成商品信息 + + Args: + category: 商品分类,可选 + + Returns: + Dict[str, Any]: 商品信息 + + Example: + >>> generator.generate_product_info("electronics") + { + 'id': 'prod_789012', + 'name': '智能手机', + 'category': 'electronics', + 'price': 2999.00, + 'description': '高性能智能手机,配备先进的处理器。', + 'brand': '华为', + 'model': 'P50 Pro', + 'sku': 'HW-P50-PRO-256GB', + 'stock': 150, + 'images': ['https://example.com/img1.jpg'], + 'created_at': '2023-10-01 12:00:00' + } + """ +``` + +## 📋 DataProvider + +数据提供者负责管理和分发测试数据。 + +### 类定义 + +```python +from src.data_manager.data_provider import DataProvider + +class DataProvider: + """测试数据提供者 + + 管理测试数据的加载、分发和同步。 + """ +``` + +### 核心方法 + +#### load_data_from_file() + +```python +def load_data_from_file(self, file_path: str, + distribution_strategy: str = "round_robin", + data_key: Optional[str] = None) -> bool: + """从文件加载数据 + + Args: + file_path: 文件路径 + distribution_strategy: 分发策略 ("round_robin"/"random"/"sequential") + data_key: 数据键名,用于标识数据集 + + Returns: + bool: 加载是否成功 + + Example: + >>> provider = DataProvider() + >>> provider.load_data_from_file("test_data/users.csv", "round_robin", "users") + True + """ +``` + +#### load_data_from_database() + +```python +def load_data_from_database(self, connection_string: str, query: str, + data_key: str, distribution_strategy: str = "round_robin") -> bool: + """从数据库加载数据 + + Args: + connection_string: 数据库连接字符串 + query: SQL查询语句 + data_key: 数据键名 + distribution_strategy: 分发策略 + + Returns: + bool: 加载是否成功 + + Example: + >>> provider.load_data_from_database( + ... "mysql://user:pass@localhost/testdb", + ... "SELECT * FROM test_users", + ... "db_users" + ... ) + """ +``` + +#### get_next_data() + +```python +def get_next_data(self, data_key: str) -> Optional[Dict[str, Any]]: + """获取下一条数据 + + Args: + data_key: 数据键名 + + Returns: + Optional[Dict[str, Any]]: 数据记录,如果没有数据则返回None + + Example: + >>> data = provider.get_next_data("users") + >>> print(data) + {'username': 'user1', 'password': 'pass1', 'email': 'user1@example.com'} + """ +``` + +#### get_random_data() + +```python +def get_random_data(self, data_key: str, count: int = 1) -> List[Dict[str, Any]]: + """获取随机数据 + + Args: + data_key: 数据键名 + count: 获取数量 + + Returns: + List[Dict[str, Any]]: 随机数据列表 + + Example: + >>> random_users = provider.get_random_data("users", 3) + >>> len(random_users) + 3 + """ +``` + +#### get_data_stats() + +```python +def get_data_stats(self, data_key: str) -> Dict[str, Any]: + """获取数据统计信息 + + Args: + data_key: 数据键名 + + Returns: + Dict[str, Any]: 统计信息 + + Example: + >>> stats = provider.get_data_stats("users") + >>> print(stats) + { + 'total_records': 1000, + 'current_index': 45, + 'distribution_strategy': 'round_robin', + 'last_accessed': '2023-12-01 10:30:00' + } + """ +``` + +## 🔄 DataDistributor + +数据分发器负责在分布式环境中同步测试数据。 + +### 类定义 + +```python +from src.data_manager.data_distributor import DataDistributor + +class DataDistributor: + """数据分发器 + + 在分布式测试环境中同步和分发测试数据。 + """ +``` + +### 核心方法 + +#### sync_data() + +```python +def sync_data(self, data_key: str, target_workers: List[str]) -> bool: + """同步数据到工作节点 + + Args: + data_key: 数据键名 + target_workers: 目标工作节点列表 + + Returns: + bool: 同步是否成功 + + Example: + >>> distributor = DataDistributor() + >>> success = distributor.sync_data("users", ["worker1", "worker2"]) + """ +``` + +#### distribute_data_range() + +```python +def distribute_data_range(self, data_key: str, worker_id: str, + total_workers: int) -> Tuple[int, int]: + """分配数据范围给工作节点 + + Args: + data_key: 数据键名 + worker_id: 工作节点ID + total_workers: 总工作节点数 + + Returns: + Tuple[int, int]: 数据范围 (start_index, end_index) + + Example: + >>> start, end = distributor.distribute_data_range("users", "worker1", 4) + >>> print(f"Worker1 处理数据范围: {start}-{end}") + """ +``` + +## 🔧 使用示例 + +### 完整数据管理流程 + +```python +from src.data_manager.data_generator import DataGenerator +from src.data_manager.data_provider import DataProvider +from src.data_manager.data_distributor import DataDistributor + +# 1. 初始化组件 +generator = DataGenerator(locale="zh_CN", seed=12345) +provider = DataProvider() +distributor = DataDistributor() + +# 2. 生成测试数据 +users_data = [] +for i in range(1000): + user = generator.generate_user_profile() + users_data.append(user) + +# 保存到文件 +import csv +with open("test_data/generated_users.csv", "w", newline="", encoding="utf-8") as f: + if users_data: + writer = csv.DictWriter(f, fieldnames=users_data[0].keys()) + writer.writeheader() + writer.writerows(users_data) + +# 3. 加载数据到提供者 +provider.load_data_from_file("test_data/generated_users.csv", "round_robin", "users") + +# 4. 在测试中使用数据 +def get_test_user(): + return provider.get_next_data("users") + +# 5. 分布式环境中同步数据 +if is_distributed_mode(): + distributor.sync_data("users", get_worker_list()) +``` + +### 自定义数据生成器 + +```python +class CustomDataGenerator(DataGenerator): + """自定义数据生成器""" + + def generate_order_info(self) -> Dict[str, Any]: + """生成订单信息""" + return { + 'order_id': f"ORD{self.generate_integer(100000, 999999)}", + 'customer_id': f"CUST{self.generate_integer(1000, 9999)}", + 'product_id': f"PROD{self.generate_integer(100, 999)}", + 'quantity': self.generate_integer(1, 5), + 'unit_price': self.generate_float(10.0, 500.0, 2), + 'total_amount': 0, # 计算得出 + 'order_date': self.generate_datetime(), + 'status': self.fake.random_element(['pending', 'confirmed', 'shipped', 'delivered']) + } + + def generate_api_request_data(self) -> Dict[str, Any]: + """生成API请求数据""" + return { + 'request_id': self.fake.uuid4(), + 'timestamp': self.generate_timestamp(), + 'method': self.fake.random_element(['GET', 'POST', 'PUT', 'DELETE']), + 'endpoint': f"/api/v1/{self.fake.random_element(['users', 'orders', 'products'])}", + 'headers': { + 'Content-Type': 'application/json', + 'User-Agent': self.fake.user_agent(), + 'X-Request-ID': self.fake.uuid4() + }, + 'payload': self.generate_json_payload() + } +``` + +### 数据提供者扩展 + +```python +class EnhancedDataProvider(DataProvider): + """增强的数据提供者""" + + def load_data_from_api(self, api_url: str, headers: Dict[str, str], + data_key: str) -> bool: + """从API加载数据""" + try: + import requests + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + data = response.json() + self._store_data(data_key, data) + return True + except Exception as e: + print(f"Failed to load data from API: {e}") + return False + + def get_weighted_data(self, data_key: str, weights: Dict[str, float]) -> Optional[Dict[str, Any]]: + """根据权重获取数据""" + import random + + data_list = self._get_data_list(data_key) + if not data_list: + return None + + # 根据权重选择数据 + choices = list(weights.keys()) + weight_values = list(weights.values()) + + selected_key = random.choices(choices, weights=weight_values)[0] + return self.get_next_data(selected_key) +``` + +## ⚠️ 注意事项 + +1. **内存管理**: 大量数据加载时注意内存使用 +2. **数据同步**: 分布式环境中确保数据一致性 +3. **文件格式**: 支持CSV、JSON、Excel等格式 +4. **数据质量**: 生成的数据应符合业务逻辑 +5. **性能优化**: 合理设置缓存和批处理大小 + +## 🔗 相关文档 + +- [性能分析API](analysis.md) - 性能分析模块API +- [监控告警API](monitoring.md) - 监控告警模块API +- [插件接口API](plugins.md) - 插件开发接口 +- [数据配置](../configuration/data-config.md) - 数据管理配置 diff --git a/docs/api/load-shapes.md b/docs/api/load-shapes.md new file mode 100644 index 0000000..03ae0c6 --- /dev/null +++ b/docs/api/load-shapes.md @@ -0,0 +1,609 @@ +# 负载模式API + +本文档详细介绍Locust性能测试框架的负载模式API接口。 + +## 📋 API概览 + +负载模式API提供了丰富的负载控制接口,支持多种负载模式: + +- **基础负载模式**: 常量负载、阶梯负载 +- **高级负载模式**: 波浪负载、尖峰负载、自适应负载 +- **自定义负载模式**: 用户自定义负载曲线 +- **负载模式管理**: 负载模式的注册和管理 + +## 🔧 基础负载模式API + +### LoadShape + +所有负载模式的基础类。 + +```python +from abc import ABC, abstractmethod +from typing import Tuple, Optional, Dict, Any +from locust import LoadTestShape + +class LoadShape(LoadTestShape, ABC): + """负载模式基类""" + + def __init__(self, **kwargs): + """ + 初始化负载模式 + + Args: + **kwargs: 负载模式参数 + """ + super().__init__() + self.start_time = None + self.config = kwargs + + @abstractmethod + def tick(self) -> Optional[Tuple[int, float]]: + """ + 获取当前时刻的负载配置 + + Returns: + Optional[Tuple[int, float]]: (用户数, 生成速率) 或 None表示测试结束 + """ + pass + + def get_current_time(self) -> float: + """ + 获取当前运行时间(秒) + + Returns: + float: 运行时间 + """ + import time + if self.start_time is None: + self.start_time = time.time() + return time.time() - self.start_time + + def get_config(self) -> Dict[str, Any]: + """ + 获取负载模式配置 + + Returns: + Dict[str, Any]: 配置字典 + """ + return self.config + + def validate_config(self) -> bool: + """ + 验证配置参数 + + Returns: + bool: 配置是否有效 + """ + return True +``` + +### ConstantLoadShape + +常量负载模式,保持固定的用户数和生成速率。 + +```python +class ConstantLoadShape(LoadShape): + """常量负载模式""" + + def __init__(self, users: int = 100, spawn_rate: float = 10, + duration: Optional[int] = None, **kwargs): + """ + 初始化常量负载模式 + + Args: + users: 用户数 + spawn_rate: 生成速率 + duration: 持续时间(秒),None表示无限制 + **kwargs: 其他参数 + """ + super().__init__(**kwargs) + self.users = users + self.spawn_rate = spawn_rate + self.duration = duration + + def tick(self) -> Optional[Tuple[int, float]]: + """ + 获取当前负载配置 + + Returns: + Optional[Tuple[int, float]]: (用户数, 生成速率) + """ + current_time = self.get_current_time() + + if self.duration and current_time >= self.duration: + return None + + return self.users, self.spawn_rate + + def validate_config(self) -> bool: + """验证配置""" + return (self.users > 0 and self.spawn_rate > 0 and + (self.duration is None or self.duration > 0)) +``` + +### StepLoadShape + +阶梯负载模式,按阶梯递增用户数。 + +```python +from typing import List, NamedTuple + +class Step(NamedTuple): + """阶梯定义""" + users: int + duration: int + spawn_rate: float = 10 + +class StepLoadShape(LoadShape): + """阶梯负载模式""" + + def __init__(self, steps: List[Step], **kwargs): + """ + 初始化阶梯负载模式 + + Args: + steps: 阶梯列表 + **kwargs: 其他参数 + """ + super().__init__(**kwargs) + self.steps = steps + self.current_step = 0 + self.step_start_time = 0 + + def tick(self) -> Optional[Tuple[int, float]]: + """ + 获取当前负载配置 + + Returns: + Optional[Tuple[int, float]]: (用户数, 生成速率) + """ + current_time = self.get_current_time() + + if self.current_step >= len(self.steps): + return None + + step = self.steps[self.current_step] + step_elapsed = current_time - self.step_start_time + + if step_elapsed >= step.duration: + self.current_step += 1 + self.step_start_time = current_time + + if self.current_step >= len(self.steps): + return None + + step = self.steps[self.current_step] + + return step.users, step.spawn_rate + + def validate_config(self) -> bool: + """验证配置""" + if not self.steps: + return False + + for step in self.steps: + if step.users <= 0 or step.duration <= 0 or step.spawn_rate <= 0: + return False + + return True +``` + +## 🌊 高级负载模式API + +### WaveLoadShape + +波浪负载模式,按正弦波形变化用户数。 + +```python +import math + +class WaveLoadShape(LoadShape): + """波浪负载模式""" + + def __init__(self, min_users: int = 10, max_users: int = 100, + wave_period: int = 300, spawn_rate: float = 10, + duration: Optional[int] = None, **kwargs): + """ + 初始化波浪负载模式 + + Args: + min_users: 最小用户数 + max_users: 最大用户数 + wave_period: 波浪周期(秒) + spawn_rate: 生成速率 + duration: 持续时间(秒) + **kwargs: 其他参数 + """ + super().__init__(**kwargs) + self.min_users = min_users + self.max_users = max_users + self.wave_period = wave_period + self.spawn_rate = spawn_rate + self.duration = duration + + def tick(self) -> Optional[Tuple[int, float]]: + """ + 获取当前负载配置 + + Returns: + Optional[Tuple[int, float]]: (用户数, 生成速率) + """ + current_time = self.get_current_time() + + if self.duration and current_time >= self.duration: + return None + + # 计算正弦波用户数 + wave_position = (current_time % self.wave_period) / self.wave_period + sine_value = math.sin(2 * math.pi * wave_position) + + # 将正弦值映射到用户数范围 + user_range = self.max_users - self.min_users + current_users = self.min_users + int((sine_value + 1) / 2 * user_range) + + return current_users, self.spawn_rate + + def validate_config(self) -> bool: + """验证配置""" + return (self.min_users > 0 and self.max_users > self.min_users and + self.wave_period > 0 and self.spawn_rate > 0 and + (self.duration is None or self.duration > 0)) +``` + +### SpikeLoadShape + +尖峰负载模式,在指定时间产生负载尖峰。 + +```python +from typing import List + +class Spike(NamedTuple): + """尖峰定义""" + start_time: int + peak_users: int + duration: int + spawn_rate: float = 50 + +class SpikeLoadShape(LoadShape): + """尖峰负载模式""" + + def __init__(self, base_users: int = 50, spikes: List[Spike] = None, + base_spawn_rate: float = 10, total_duration: int = 600, **kwargs): + """ + 初始化尖峰负载模式 + + Args: + base_users: 基础用户数 + spikes: 尖峰列表 + base_spawn_rate: 基础生成速率 + total_duration: 总持续时间 + **kwargs: 其他参数 + """ + super().__init__(**kwargs) + self.base_users = base_users + self.spikes = spikes or [] + self.base_spawn_rate = base_spawn_rate + self.total_duration = total_duration + + def tick(self) -> Optional[Tuple[int, float]]: + """ + 获取当前负载配置 + + Returns: + Optional[Tuple[int, float]]: (用户数, 生成速率) + """ + current_time = self.get_current_time() + + if current_time >= self.total_duration: + return None + + # 检查是否在尖峰时间内 + for spike in self.spikes: + spike_end = spike.start_time + spike.duration + if spike.start_time <= current_time < spike_end: + return spike.peak_users, spike.spawn_rate + + # 返回基础负载 + return self.base_users, self.base_spawn_rate + + def validate_config(self) -> bool: + """验证配置""" + if (self.base_users <= 0 or self.base_spawn_rate <= 0 or + self.total_duration <= 0): + return False + + for spike in self.spikes: + if (spike.start_time < 0 or spike.peak_users <= 0 or + spike.duration <= 0 or spike.spawn_rate <= 0): + return False + + if spike.start_time + spike.duration > self.total_duration: + return False + + return True +``` + +### AdaptiveLoadShape + +自适应负载模式,根据性能指标动态调整负载。 + +```python +class AdaptiveLoadShape(LoadShape): + """自适应负载模式""" + + def __init__(self, initial_users: int = 50, max_users: int = 500, + target_response_time: float = 1000, adjustment_interval: int = 30, + spawn_rate: float = 10, **kwargs): + """ + 初始化自适应负载模式 + + Args: + initial_users: 初始用户数 + max_users: 最大用户数 + target_response_time: 目标响应时间(ms) + adjustment_interval: 调整间隔(秒) + spawn_rate: 生成速率 + **kwargs: 其他参数 + """ + super().__init__(**kwargs) + self.initial_users = initial_users + self.max_users = max_users + self.target_response_time = target_response_time + self.adjustment_interval = adjustment_interval + self.spawn_rate = spawn_rate + + self.current_users = initial_users + self.last_adjustment_time = 0 + self.performance_history = [] + + def tick(self) -> Optional[Tuple[int, float]]: + """ + 获取当前负载配置 + + Returns: + Optional[Tuple[int, float]]: (用户数, 生成速率) + """ + current_time = self.get_current_time() + + # 检查是否需要调整负载 + if current_time - self.last_adjustment_time >= self.adjustment_interval: + self._adjust_load() + self.last_adjustment_time = current_time + + return self.current_users, self.spawn_rate + + def _adjust_load(self): + """根据性能指标调整负载""" + # 获取当前性能指标 + current_response_time = self._get_current_response_time() + + if current_response_time is None: + return + + # 根据响应时间调整用户数 + if current_response_time > self.target_response_time * 1.2: + # 响应时间过高,减少用户数 + self.current_users = max(1, int(self.current_users * 0.9)) + elif current_response_time < self.target_response_time * 0.8: + # 响应时间良好,增加用户数 + self.current_users = min(self.max_users, int(self.current_users * 1.1)) + + # 记录性能历史 + self.performance_history.append({ + 'time': self.get_current_time(), + 'users': self.current_users, + 'response_time': current_response_time + }) + + def _get_current_response_time(self) -> Optional[float]: + """ + 获取当前平均响应时间 + + Returns: + Optional[float]: 平均响应时间(ms) + """ + # 这里需要从Locust统计信息中获取实际的响应时间 + # 实际实现中需要访问Locust的stats对象 + try: + from locust import stats + if stats.total.num_requests > 0: + return stats.total.avg_response_time + except: + pass + return None + + def validate_config(self) -> bool: + """验证配置""" + return (self.initial_users > 0 and self.max_users > 0 and + self.target_response_time > 0 and self.adjustment_interval > 0 and + self.spawn_rate > 0) +``` + +## 🔧 负载模式管理API + +### LoadShapeManager + +负载模式管理器,用于注册和管理负载模式。 + +```python +from typing import Dict, Type, Optional + +class LoadShapeManager: + """负载模式管理器""" + + def __init__(self): + self._shapes: Dict[str, Type[LoadShape]] = {} + self._register_builtin_shapes() + + def register_shape(self, name: str, shape_class: Type[LoadShape]) -> bool: + """ + 注册负载模式 + + Args: + name: 负载模式名称 + shape_class: 负载模式类 + + Returns: + bool: 注册是否成功 + """ + if not issubclass(shape_class, LoadShape): + return False + + self._shapes[name] = shape_class + return True + + def get_shape(self, name: str) -> Optional[Type[LoadShape]]: + """ + 获取负载模式类 + + Args: + name: 负载模式名称 + + Returns: + Optional[Type[LoadShape]]: 负载模式类 + """ + return self._shapes.get(name) + + def create_shape(self, name: str, **kwargs) -> Optional[LoadShape]: + """ + 创建负载模式实例 + + Args: + name: 负载模式名称 + **kwargs: 负载模式参数 + + Returns: + Optional[LoadShape]: 负载模式实例 + """ + shape_class = self.get_shape(name) + if shape_class: + return shape_class(**kwargs) + return None + + def list_shapes(self) -> List[str]: + """ + 列出所有注册的负载模式 + + Returns: + List[str]: 负载模式名称列表 + """ + return list(self._shapes.keys()) + + def _register_builtin_shapes(self): + """注册内置负载模式""" + self.register_shape("constant", ConstantLoadShape) + self.register_shape("step", StepLoadShape) + self.register_shape("wave", WaveLoadShape) + self.register_shape("spike", SpikeLoadShape) + self.register_shape("adaptive", AdaptiveLoadShape) + +# 全局负载模式管理器实例 +load_shape_manager = LoadShapeManager() +``` + +## 📊 负载模式工具API + +### LoadShapeValidator + +负载模式验证器。 + +```python +class LoadShapeValidator: + """负载模式验证器""" + + @staticmethod + def validate_shape_config(shape_type: str, config: Dict[str, Any]) -> bool: + """ + 验证负载模式配置 + + Args: + shape_type: 负载模式类型 + config: 配置字典 + + Returns: + bool: 配置是否有效 + """ + shape_class = load_shape_manager.get_shape(shape_type) + if not shape_class: + return False + + try: + shape = shape_class(**config) + return shape.validate_config() + except Exception: + return False + + @staticmethod + def estimate_duration(shape_type: str, config: Dict[str, Any]) -> Optional[int]: + """ + 估算负载模式持续时间 + + Args: + shape_type: 负载模式类型 + config: 配置字典 + + Returns: + Optional[int]: 估算的持续时间(秒) + """ + if shape_type == "constant": + return config.get("duration") + elif shape_type == "step": + steps = config.get("steps", []) + return sum(step.duration for step in steps) if steps else None + elif shape_type in ["wave", "spike", "adaptive"]: + return config.get("duration") or config.get("total_duration") + + return None +``` + +## 📚 使用示例 + +### 创建自定义负载模式 + +```python +class CustomLoadShape(LoadShape): + """自定义负载模式示例""" + + def __init__(self, pattern: str = "linear", **kwargs): + super().__init__(**kwargs) + self.pattern = pattern + self.max_users = kwargs.get("max_users", 100) + self.duration = kwargs.get("duration", 300) + self.spawn_rate = kwargs.get("spawn_rate", 10) + + def tick(self) -> Optional[Tuple[int, float]]: + current_time = self.get_current_time() + + if current_time >= self.duration: + return None + + if self.pattern == "linear": + # 线性增长 + progress = current_time / self.duration + users = int(self.max_users * progress) + elif self.pattern == "exponential": + # 指数增长 + progress = current_time / self.duration + users = int(self.max_users * (progress ** 2)) + else: + users = self.max_users + + return max(1, users), self.spawn_rate + +# 注册自定义负载模式 +load_shape_manager.register_shape("custom", CustomLoadShape) + +# 使用负载模式 +shape = load_shape_manager.create_shape("custom", + pattern="exponential", + max_users=200, + duration=600, + spawn_rate=15) +``` + +## 📚 相关文档 + +- [负载模式开发](../development/load-shape-development.md) - 负载模式开发指南 +- [基础示例](../examples/basic-examples.md) - 负载模式使用示例 +- [高级示例](../examples/advanced-usage.md) - 高级负载模式示例 +- [配置参考](../configuration/framework-config.md) - 负载模式配置 diff --git a/docs/api/monitoring.md b/docs/api/monitoring.md new file mode 100644 index 0000000..6c12e84 --- /dev/null +++ b/docs/api/monitoring.md @@ -0,0 +1,620 @@ +# 监控告警API参考 + +本文档详细介绍监控告警模块的API接口,包括性能监控器、告警管理器和通知服务。 + +## 📊 PerformanceMonitor + +性能监控器提供实时性能指标收集和监控功能。 + +### 类定义 + +```python +from src.monitoring.performance_monitor import PerformanceMonitor + +class PerformanceMonitor: + """性能监控器 + + 提供实时性能监控功能,包括指标收集、阈值检查、 + 告警触发等核心监控能力。 + """ +``` + +### 构造函数 + +```python +def __init__(self, config: Optional[Dict[str, Any]] = None): + """初始化性能监控器 + + Args: + config: 监控配置参数 + - interval: 监控间隔(秒),默认5 + - buffer_size: 缓冲区大小,默认1000 + - metrics_retention: 指标保留时间(秒),默认3600 + - alert_cooldown: 告警冷却时间(秒),默认300 + + Example: + >>> config = { + ... 'interval': 10, + ... 'buffer_size': 2000, + ... 'alert_cooldown': 600 + ... } + >>> monitor = PerformanceMonitor(config) + """ +``` + +### 核心方法 + +#### start_monitoring() + +```python +def start_monitoring(self) -> bool: + """开始监控 + + 启动监控线程,开始收集性能指标。 + + Returns: + bool: 启动是否成功 + + Example: + >>> monitor = PerformanceMonitor() + >>> success = monitor.start_monitoring() + >>> print(f"监控启动{'成功' if success else '失败'}") + """ +``` + +#### stop_monitoring() + +```python +def stop_monitoring(self) -> bool: + """停止监控 + + 停止监控线程,清理资源。 + + Returns: + bool: 停止是否成功 + + Example: + >>> monitor.stop_monitoring() + """ +``` + +#### collect_metrics() + +```python +def collect_metrics(self) -> Dict[str, Any]: + """收集当前性能指标 + + Returns: + Dict[str, Any]: 当前性能指标 + - timestamp: 时间戳 + - response_time: 响应时间统计 + - throughput: 吞吐量统计 + - error_rate: 错误率 + - active_users: 活跃用户数 + - system_metrics: 系统指标(可选) + + Example: + >>> metrics = monitor.collect_metrics() + >>> print(f"当前TPS: {metrics['throughput']['current']}") + """ +``` + +#### register_custom_metric() + +```python +def register_custom_metric(self, name: str, metric_type: str, + description: str = "") -> bool: + """注册自定义指标 + + Args: + name: 指标名称 + metric_type: 指标类型 (counter/gauge/histogram) + description: 指标描述 + + Returns: + bool: 注册是否成功 + + Example: + >>> success = monitor.register_custom_metric( + ... "business_transactions", + ... "counter", + ... "业务交易计数" + ... ) + """ +``` + +#### record_metric() + +```python +def record_metric(self, name: str, value: Union[int, float], + labels: Optional[Dict[str, str]] = None) -> bool: + """记录指标值 + + Args: + name: 指标名称 + value: 指标值 + labels: 标签字典(可选) + + Returns: + bool: 记录是否成功 + + Example: + >>> monitor.record_metric("response_time", 500.0, {"endpoint": "/api/users"}) + >>> monitor.record_metric("error_count", 1, {"error_type": "timeout"}) + """ +``` + +#### get_metrics_history() + +```python +def get_metrics_history(self, metric_name: str, + time_range: Optional[Tuple[float, float]] = None) -> List[Dict[str, Any]]: + """获取指标历史数据 + + Args: + metric_name: 指标名称 + time_range: 时间范围(开始时间戳, 结束时间戳),可选 + + Returns: + List[Dict[str, Any]]: 历史数据列表 + + Example: + >>> import time + >>> end_time = time.time() + >>> start_time = end_time - 3600 # 最近1小时 + >>> history = monitor.get_metrics_history("response_time", (start_time, end_time)) + """ +``` + +## 🚨 AlertManager + +告警管理器负责告警规则管理、告警触发和生命周期管理。 + +### 类定义 + +```python +from src.monitoring.alert_manager import AlertManager + +class AlertManager: + """告警管理器 + + 管理告警规则、触发告警、处理告警生命周期。 + """ +``` + +### 核心方法 + +#### add_alert_rule() + +```python +def add_alert_rule(self, rule: Dict[str, Any]) -> bool: + """添加告警规则 + + Args: + rule: 告警规则配置 + - name: 规则名称 + - metric: 监控指标 + - condition: 触发条件 + - threshold: 阈值 + - duration: 持续时间 + - severity: 严重级别 + - message: 告警消息 + - actions: 告警动作列表 + + Returns: + bool: 添加是否成功 + + Example: + >>> rule = { + ... "name": "high_response_time", + ... "metric": "avg_response_time", + ... "condition": ">", + ... "threshold": 2000, + ... "duration": 300, + ... "severity": "warning", + ... "message": "平均响应时间过高", + ... "actions": ["notify", "log"] + ... } + >>> alert_manager.add_alert_rule(rule) + """ +``` + +#### remove_alert_rule() + +```python +def remove_alert_rule(self, rule_name: str) -> bool: + """移除告警规则 + + Args: + rule_name: 规则名称 + + Returns: + bool: 移除是否成功 + + Example: + >>> alert_manager.remove_alert_rule("high_response_time") + """ +``` + +#### trigger_alert() + +```python +def trigger_alert(self, alert_name: str, message: str, + severity: str = "warning", + metadata: Optional[Dict[str, Any]] = None) -> str: + """手动触发告警 + + Args: + alert_name: 告警名称 + message: 告警消息 + severity: 严重级别 (info/warning/critical) + metadata: 附加元数据 + + Returns: + str: 告警ID + + Example: + >>> alert_id = alert_manager.trigger_alert( + ... "manual_alert", + ... "手动触发的测试告警", + ... "warning", + ... {"source": "manual", "test_id": "test_001"} + ... ) + """ +``` + +#### resolve_alert() + +```python +def resolve_alert(self, alert_id: str, resolution_note: str = "") -> bool: + """解决告警 + + Args: + alert_id: 告警ID + resolution_note: 解决说明 + + Returns: + bool: 解决是否成功 + + Example: + >>> alert_manager.resolve_alert("alert_123", "问题已修复") + """ +``` + +#### get_active_alerts() + +```python +def get_active_alerts(self, severity: Optional[str] = None) -> List[Dict[str, Any]]: + """获取活跃告警列表 + + Args: + severity: 过滤严重级别,可选 + + Returns: + List[Dict[str, Any]]: 活跃告警列表 + + Example: + >>> active_alerts = alert_manager.get_active_alerts("critical") + >>> print(f"严重告警数量: {len(active_alerts)}") + """ +``` + +#### get_alert_history() + +```python +def get_alert_history(self, time_range: Optional[Tuple[float, float]] = None, + limit: int = 100) -> List[Dict[str, Any]]: + """获取告警历史 + + Args: + time_range: 时间范围,可选 + limit: 返回数量限制 + + Returns: + List[Dict[str, Any]]: 告警历史列表 + + Example: + >>> history = alert_manager.get_alert_history(limit=50) + """ +``` + +## 📢 NotificationService + +通知服务负责多渠道消息发送和通知管理。 + +### 类定义 + +```python +from src.monitoring.notification_service import NotificationService + +class NotificationService: + """通知服务 + + 支持多种通知渠道的消息发送服务。 + """ +``` + +### 核心方法 + +#### send_notification() + +```python +def send_notification(self, message: str, channels: List[str], + priority: str = "normal", + metadata: Optional[Dict[str, Any]] = None) -> Dict[str, bool]: + """发送通知 + + Args: + message: 通知消息 + channels: 通知渠道列表 + priority: 优先级 (low/normal/high/urgent) + metadata: 附加元数据 + + Returns: + Dict[str, bool]: 各渠道发送结果 + + Example: + >>> result = notification_service.send_notification( + ... "测试完成,请查看报告", + ... ["feishu", "email"], + ... "normal", + ... {"test_id": "test_001", "report_url": "http://example.com/report"} + ... ) + >>> print(f"飞书发送结果: {result['feishu']}") + """ +``` + +#### send_alert_notification() + +```python +def send_alert_notification(self, alert: Dict[str, Any]) -> Dict[str, bool]: + """发送告警通知 + + Args: + alert: 告警信息 + - id: 告警ID + - name: 告警名称 + - message: 告警消息 + - severity: 严重级别 + - timestamp: 时间戳 + - metadata: 元数据 + + Returns: + Dict[str, bool]: 各渠道发送结果 + + Example: + >>> alert = { + ... "id": "alert_123", + ... "name": "high_error_rate", + ... "message": "错误率超过阈值", + ... "severity": "critical", + ... "timestamp": time.time() + ... } + >>> result = notification_service.send_alert_notification(alert) + """ +``` + +#### register_channel() + +```python +def register_channel(self, channel_name: str, channel_config: Dict[str, Any]) -> bool: + """注册通知渠道 + + Args: + channel_name: 渠道名称 + channel_config: 渠道配置 + + Returns: + bool: 注册是否成功 + + Example: + >>> config = { + ... "webhook_url": "https://hooks.slack.com/xxx", + ... "channel": "#alerts", + ... "username": "LocustBot" + ... } + >>> notification_service.register_channel("slack", config) + """ +``` + +#### get_channel_status() + +```python +def get_channel_status(self) -> Dict[str, Dict[str, Any]]: + """获取渠道状态 + + Returns: + Dict[str, Dict[str, Any]]: 各渠道状态信息 + + Example: + >>> status = notification_service.get_channel_status() + >>> for channel, info in status.items(): + ... print(f"{channel}: {'可用' if info['available'] else '不可用'}") + """ +``` + +## 📈 MetricsCollector + +指标收集器负责从各种数据源收集性能指标。 + +### 类定义 + +```python +from src.monitoring.metrics_collector import MetricsCollector + +class MetricsCollector: + """指标收集器 + + 从多种数据源收集性能指标数据。 + """ +``` + +### 核心方法 + +#### collect_locust_metrics() + +```python +def collect_locust_metrics(self, environment) -> Dict[str, Any]: + """收集Locust指标 + + Args: + environment: Locust环境对象 + + Returns: + Dict[str, Any]: Locust指标数据 + + Example: + >>> metrics = collector.collect_locust_metrics(environment) + >>> print(f"总请求数: {metrics['total_requests']}") + """ +``` + +#### collect_system_metrics() + +```python +def collect_system_metrics(self) -> Dict[str, Any]: + """收集系统指标 + + Returns: + Dict[str, Any]: 系统指标数据 + - cpu_usage: CPU使用率 + - memory_usage: 内存使用率 + - disk_usage: 磁盘使用率 + - network_io: 网络IO统计 + + Example: + >>> system_metrics = collector.collect_system_metrics() + >>> print(f"CPU使用率: {system_metrics['cpu_usage']}%") + """ +``` + +#### collect_custom_metrics() + +```python +def collect_custom_metrics(self, metric_sources: List[str]) -> Dict[str, Any]: + """收集自定义指标 + + Args: + metric_sources: 指标源列表 + + Returns: + Dict[str, Any]: 自定义指标数据 + + Example: + >>> custom_metrics = collector.collect_custom_metrics(["database", "cache"]) + """ +``` + +## 🔧 使用示例 + +### 完整监控流程 + +```python +from src.monitoring.performance_monitor import PerformanceMonitor +from src.monitoring.alert_manager import AlertManager +from src.monitoring.notification_service import NotificationService + +# 1. 初始化监控组件 +monitor = PerformanceMonitor({ + 'interval': 5, + 'buffer_size': 2000 +}) + +alert_manager = AlertManager() +notification_service = NotificationService() + +# 2. 配置告警规则 +alert_rules = [ + { + "name": "high_response_time", + "metric": "avg_response_time", + "condition": ">", + "threshold": 2000, + "duration": 300, + "severity": "warning", + "message": "平均响应时间超过2秒" + }, + { + "name": "high_error_rate", + "metric": "error_rate", + "condition": ">", + "threshold": 5.0, + "duration": 120, + "severity": "critical", + "message": "错误率超过5%" + } +] + +for rule in alert_rules: + alert_manager.add_alert_rule(rule) + +# 3. 配置通知渠道 +notification_service.register_channel("feishu", { + "webhook_url": "https://open.feishu.cn/open-apis/bot/v2/hook/xxx" +}) + +# 4. 启动监控 +monitor.start_monitoring() + +# 5. 注册自定义指标 +monitor.register_custom_metric("business_success_rate", "gauge") + +# 6. 在测试过程中记录指标 +def on_request_success(request_type, name, response_time, response_length, **kwargs): + monitor.record_metric("response_time", response_time, {"endpoint": name}) + monitor.record_metric("business_success_rate", 1.0) + +def on_request_failure(request_type, name, response_time, response_length, exception, **kwargs): + monitor.record_metric("response_time", response_time, {"endpoint": name}) + monitor.record_metric("business_success_rate", 0.0) + + # 触发告警 + if response_time > 5000: + alert_manager.trigger_alert( + "slow_request", + f"请求 {name} 响应时间过长: {response_time}ms", + "warning" + ) + +# 7. 停止监控 +monitor.stop_monitoring() +``` + +### 自定义监控插件 + +```python +from src.monitoring.performance_monitor import PerformanceMonitor + +class DatabaseMonitor(PerformanceMonitor): + """数据库监控插件""" + + def __init__(self, db_config): + super().__init__() + self.db_config = db_config + + def collect_custom_metrics(self): + """收集数据库指标""" + # 连接数据库 + # 查询性能指标 + # 返回指标数据 + return { + "active_connections": 50, + "query_latency": 100, + "cache_hit_rate": 0.95 + } +``` + +## ⚠️ 注意事项 + +1. **性能影响**: 监控会消耗系统资源,合理设置监控间隔 +2. **内存管理**: 定期清理历史数据,避免内存泄漏 +3. **告警风暴**: 设置告警冷却时间,避免重复告警 +4. **网络依赖**: 通知服务依赖网络,需要处理网络异常 +5. **配置验证**: 启动前验证监控配置的正确性 + +## 🔗 相关文档 + +- [性能分析API](analysis.md) - 性能分析模块API +- [数据管理API](data-manager.md) - 数据管理模块API +- [插件接口API](plugins.md) - 插件开发接口 +- [监控配置](../configuration/monitoring-config.md) - 监控配置说明 diff --git a/docs/api/plugins.md b/docs/api/plugins.md new file mode 100644 index 0000000..f985ded --- /dev/null +++ b/docs/api/plugins.md @@ -0,0 +1,694 @@ +# 插件接口API + +本文档详细介绍Locust性能测试框架的插件开发接口API。 + +## 📋 API概览 + +插件系统提供了丰富的API接口,支持多种类型的插件开发: + +- **基础插件API**: 所有插件的基础接口 +- **Locust插件API**: 扩展Locust核心功能 +- **报告插件API**: 自定义报告生成 +- **监控插件API**: 扩展监控能力 +- **数据插件API**: 数据源和处理扩展 +- **通知插件API**: 通知渠道扩展 + +## 🔌 基础插件API + +### BasePlugin + +所有插件的基础类,提供插件的基本生命周期管理。 + +```python +from abc import ABC, abstractmethod +from typing import Dict, Any, Optional + +class BasePlugin(ABC): + """插件基类""" + + def __init__(self, config: Optional[Dict[str, Any]] = None): + """ + 初始化插件 + + Args: + config: 插件配置字典 + """ + self.config = config or {} + self.enabled = True + self.logger = self._setup_logger() + self.name = self.__class__.__name__ + self.version = "1.0.0" + + @abstractmethod + def initialize(self) -> bool: + """ + 插件初始化 + + Returns: + bool: 初始化是否成功 + """ + pass + + @abstractmethod + def cleanup(self) -> None: + """插件清理资源""" + pass + + def enable(self) -> None: + """启用插件""" + self.enabled = True + + def disable(self) -> None: + """禁用插件""" + self.enabled = False + + def is_enabled(self) -> bool: + """检查插件是否启用""" + return self.enabled + + def get_info(self) -> Dict[str, Any]: + """ + 获取插件信息 + + Returns: + Dict[str, Any]: 插件信息字典 + """ + return { + "name": self.name, + "version": self.version, + "enabled": self.enabled, + "config": self.config + } + + def validate_config(self, config: Dict[str, Any]) -> bool: + """ + 验证配置 + + Args: + config: 配置字典 + + Returns: + bool: 配置是否有效 + """ + return True + + def _setup_logger(self): + """设置日志记录器""" + import logging + return logging.getLogger(f"plugin.{self.__class__.__name__}") +``` + +## 🦗 Locust插件API + +### LocustPlugin + +扩展Locust核心功能的插件接口。 + +```python +from locust import events +from locust.env import Environment + +class LocustPlugin(BasePlugin): + """Locust功能扩展插件""" + + def __init__(self, config: Optional[Dict[str, Any]] = None): + super().__init__(config) + self.environment: Optional[Environment] = None + + def set_environment(self, environment: Environment) -> None: + """ + 设置Locust环境 + + Args: + environment: Locust环境对象 + """ + self.environment = environment + + def on_test_start(self, environment: Environment, **kwargs) -> None: + """ + 测试开始时调用 + + Args: + environment: Locust环境对象 + **kwargs: 额外参数 + """ + pass + + def on_test_stop(self, environment: Environment, **kwargs) -> None: + """ + 测试结束时调用 + + Args: + environment: Locust环境对象 + **kwargs: 额外参数 + """ + pass + + def on_user_add(self, user_instance, **kwargs) -> None: + """ + 用户添加时调用 + + Args: + user_instance: 用户实例 + **kwargs: 额外参数 + """ + pass + + def on_user_remove(self, user_instance, **kwargs) -> None: + """ + 用户移除时调用 + + Args: + user_instance: 用户实例 + **kwargs: 额外参数 + """ + pass + + def on_request_success(self, request_type: str, name: str, + response_time: float, response_length: int, **kwargs) -> None: + """ + 请求成功时调用 + + Args: + request_type: 请求类型 + name: 请求名称 + response_time: 响应时间 + response_length: 响应长度 + **kwargs: 额外参数 + """ + pass + + def on_request_failure(self, request_type: str, name: str, + response_time: float, response_length: int, + exception: Exception, **kwargs) -> None: + """ + 请求失败时调用 + + Args: + request_type: 请求类型 + name: 请求名称 + response_time: 响应时间 + response_length: 响应长度 + exception: 异常对象 + **kwargs: 额外参数 + """ + pass + + def register_events(self) -> None: + """注册事件监听器""" + if self.environment: + events.test_start.add_listener(self.on_test_start) + events.test_stop.add_listener(self.on_test_stop) + events.user_add.add_listener(self.on_user_add) + events.user_remove.add_listener(self.on_user_remove) + events.request_success.add_listener(self.on_request_success) + events.request_failure.add_listener(self.on_request_failure) +``` + +## 📊 报告插件API + +### ReportPlugin + +自定义报告生成插件接口。 + +```python +from typing import List, Dict, Any +from pathlib import Path + +class ReportPlugin(BasePlugin): + """报告生成插件""" + + def generate_report(self, stats: Dict[str, Any], + output_path: Path, **kwargs) -> bool: + """ + 生成报告 + + Args: + stats: 统计数据 + output_path: 输出路径 + **kwargs: 额外参数 + + Returns: + bool: 生成是否成功 + """ + pass + + def format_data(self, data: Any, format_type: str) -> str: + """ + 格式化数据 + + Args: + data: 原始数据 + format_type: 格式类型 (html, json, csv, markdown) + + Returns: + str: 格式化后的数据 + """ + pass + + def export_report(self, report_content: str, + output_path: Path, format_type: str) -> bool: + """ + 导出报告 + + Args: + report_content: 报告内容 + output_path: 输出路径 + format_type: 格式类型 + + Returns: + bool: 导出是否成功 + """ + pass + + def get_supported_formats(self) -> List[str]: + """ + 获取支持的格式列表 + + Returns: + List[str]: 支持的格式列表 + """ + return ["html", "json", "csv", "markdown"] + + def validate_template(self, template_path: Path) -> bool: + """ + 验证模板文件 + + Args: + template_path: 模板文件路径 + + Returns: + bool: 模板是否有效 + """ + pass +``` + +## 📈 监控插件API + +### MonitorPlugin + +扩展监控能力的插件接口。 + +```python +from typing import Dict, List, Any, Optional +from datetime import datetime + +class MonitorPlugin(BasePlugin): + """监控插件""" + + def collect_metrics(self) -> Dict[str, Any]: + """ + 收集监控指标 + + Returns: + Dict[str, Any]: 监控指标数据 + """ + pass + + def check_thresholds(self, metrics: Dict[str, Any]) -> List[Dict[str, Any]]: + """ + 检查阈值 + + Args: + metrics: 监控指标 + + Returns: + List[Dict[str, Any]]: 告警列表 + """ + pass + + def trigger_alert(self, alert_data: Dict[str, Any]) -> bool: + """ + 触发告警 + + Args: + alert_data: 告警数据 + + Returns: + bool: 告警是否成功触发 + """ + pass + + def get_metric_definition(self, metric_name: str) -> Optional[Dict[str, Any]]: + """ + 获取指标定义 + + Args: + metric_name: 指标名称 + + Returns: + Optional[Dict[str, Any]]: 指标定义 + """ + pass + + def register_metric(self, metric_name: str, + metric_config: Dict[str, Any]) -> bool: + """ + 注册新指标 + + Args: + metric_name: 指标名称 + metric_config: 指标配置 + + Returns: + bool: 注册是否成功 + """ + pass + + def start_monitoring(self) -> bool: + """ + 开始监控 + + Returns: + bool: 启动是否成功 + """ + pass + + def stop_monitoring(self) -> bool: + """ + 停止监控 + + Returns: + bool: 停止是否成功 + """ + pass +``` + +## 💾 数据插件API + +### DataPlugin + +数据源和数据处理扩展插件接口。 + +```python +from typing import Iterator, List, Dict, Any, Optional + +class DataPlugin(BasePlugin): + """数据插件""" + + def load_data(self, source_config: Dict[str, Any]) -> Iterator[Dict[str, Any]]: + """ + 加载数据 + + Args: + source_config: 数据源配置 + + Yields: + Dict[str, Any]: 数据记录 + """ + pass + + def process_data(self, raw_data: Any) -> Any: + """ + 处理数据 + + Args: + raw_data: 原始数据 + + Returns: + Any: 处理后的数据 + """ + pass + + def distribute_data(self, data: List[Any], + strategy: str, node_count: int) -> List[List[Any]]: + """ + 分发数据 + + Args: + data: 数据列表 + strategy: 分发策略 (round_robin, random, sequential) + node_count: 节点数量 + + Returns: + List[List[Any]]: 分发后的数据 + """ + pass + + def validate_data(self, data: Any, schema: Dict[str, Any]) -> bool: + """ + 验证数据 + + Args: + data: 数据 + schema: 数据模式 + + Returns: + bool: 数据是否有效 + """ + pass + + def get_data_schema(self) -> Dict[str, Any]: + """ + 获取数据模式 + + Returns: + Dict[str, Any]: 数据模式定义 + """ + pass + + def cache_data(self, key: str, data: Any, ttl: Optional[int] = None) -> bool: + """ + 缓存数据 + + Args: + key: 缓存键 + data: 数据 + ttl: 生存时间(秒) + + Returns: + bool: 缓存是否成功 + """ + pass + + def get_cached_data(self, key: str) -> Optional[Any]: + """ + 获取缓存数据 + + Args: + key: 缓存键 + + Returns: + Optional[Any]: 缓存的数据 + """ + pass +``` + +## 📢 通知插件API + +### NotificationPlugin + +通知渠道扩展插件接口。 + +```python +from typing import List, Dict, Any, Optional + +class NotificationPlugin(BasePlugin): + """通知插件""" + + def send_notification(self, message: str, + recipients: List[str], **kwargs) -> bool: + """ + 发送通知 + + Args: + message: 消息内容 + recipients: 接收者列表 + **kwargs: 额外参数 + + Returns: + bool: 发送是否成功 + """ + pass + + def format_message(self, data: Dict[str, Any], + template: str) -> str: + """ + 格式化消息 + + Args: + data: 数据字典 + template: 消息模板 + + Returns: + str: 格式化后的消息 + """ + pass + + def validate_config(self, config: Dict[str, Any]) -> bool: + """ + 验证配置 + + Args: + config: 配置字典 + + Returns: + bool: 配置是否有效 + """ + pass + + def test_connection(self) -> bool: + """ + 测试连接 + + Returns: + bool: 连接是否正常 + """ + pass + + def get_supported_formats(self) -> List[str]: + """ + 获取支持的消息格式 + + Returns: + List[str]: 支持的格式列表 + """ + return ["text", "markdown", "html"] + + def send_rich_message(self, title: str, content: str, + message_type: str, recipients: List[str]) -> bool: + """ + 发送富文本消息 + + Args: + title: 消息标题 + content: 消息内容 + message_type: 消息类型 (info, warning, error) + recipients: 接收者列表 + + Returns: + bool: 发送是否成功 + """ + pass +``` + +## 🔧 插件管理API + +### PluginManager + +插件管理器API,用于插件的注册、加载和管理。 + +```python +class PluginManager: + """插件管理器""" + + def register_plugin(self, plugin_class: type, + plugin_config: Optional[Dict[str, Any]] = None) -> bool: + """ + 注册插件 + + Args: + plugin_class: 插件类 + plugin_config: 插件配置 + + Returns: + bool: 注册是否成功 + """ + pass + + def load_plugin(self, plugin_name: str) -> Optional[BasePlugin]: + """ + 加载插件 + + Args: + plugin_name: 插件名称 + + Returns: + Optional[BasePlugin]: 插件实例 + """ + pass + + def unload_plugin(self, plugin_name: str) -> bool: + """ + 卸载插件 + + Args: + plugin_name: 插件名称 + + Returns: + bool: 卸载是否成功 + """ + pass + + def get_plugin(self, plugin_name: str) -> Optional[BasePlugin]: + """ + 获取插件实例 + + Args: + plugin_name: 插件名称 + + Returns: + Optional[BasePlugin]: 插件实例 + """ + pass + + def list_plugins(self, plugin_type: Optional[str] = None) -> List[Dict[str, Any]]: + """ + 列出插件 + + Args: + plugin_type: 插件类型过滤 + + Returns: + List[Dict[str, Any]]: 插件信息列表 + """ + pass + + def enable_plugin(self, plugin_name: str) -> bool: + """ + 启用插件 + + Args: + plugin_name: 插件名称 + + Returns: + bool: 启用是否成功 + """ + pass + + def disable_plugin(self, plugin_name: str) -> bool: + """ + 禁用插件 + + Args: + plugin_name: 插件名称 + + Returns: + bool: 禁用是否成功 + """ + pass +``` + +## 📚 使用示例 + +### 创建自定义Locust插件 + +```python +class CustomLocustPlugin(LocustPlugin): + def initialize(self) -> bool: + self.logger.info("Custom Locust plugin initialized") + return True + + def cleanup(self) -> None: + self.logger.info("Custom Locust plugin cleaned up") + + def on_test_start(self, environment, **kwargs): + self.logger.info("Test started with custom plugin") + + def on_request_success(self, request_type, name, response_time, response_length, **kwargs): + if response_time > 1000: # 响应时间超过1秒 + self.logger.warning(f"Slow request detected: {name} took {response_time}ms") + +# 注册插件 +plugin_manager.register_plugin(CustomLocustPlugin, { + "log_level": "INFO", + "slow_request_threshold": 1000 +}) +``` + +## 📚 相关文档 + +- [插件开发指南](../development/plugin-development.md) - 详细的插件开发教程 +- [插件系统架构](../architecture/plugin-system.md) - 插件系统设计 +- [插件配置](../configuration/plugin-config.md) - 插件配置说明 +- [示例代码](../examples/advanced-usage.md) - 插件使用示例 diff --git a/docs/api/utilities.md b/docs/api/utilities.md new file mode 100644 index 0000000..fccc029 --- /dev/null +++ b/docs/api/utilities.md @@ -0,0 +1,933 @@ +# 工具类API + +本文档详细介绍Locust性能测试框架的工具类和辅助函数API。 + +## 📋 API概览 + +工具类API提供了丰富的辅助功能,包括: + +- **文件操作工具**: 文件读写、路径处理 +- **报告生成工具**: 报告格式化和生成 +- **日志管理工具**: 日志配置和管理 +- **集合点工具**: 用户同步和集合点 +- **通用工具函数**: 常用的辅助函数 + +## 📁 文件操作工具 + +### FileOperationUtil + +文件操作工具类,提供文件和目录的常用操作。 + +```python +import os +import json +import csv +import yaml +from typing import List, Dict, Any, Optional +from pathlib import Path + +class FileOperationUtil: + """文件操作工具类""" + + @staticmethod + def read_file(file_path: str, encoding: str = 'utf-8') -> str: + """ + 读取文件内容 + + Args: + file_path: 文件路径 + encoding: 文件编码 + + Returns: + str: 文件内容 + + Raises: + FileNotFoundError: 文件不存在 + IOError: 读取失败 + """ + try: + with open(file_path, 'r', encoding=encoding) as file: + return file.read() + except FileNotFoundError: + raise FileNotFoundError(f"File not found: {file_path}") + except Exception as e: + raise IOError(f"Failed to read file {file_path}: {e}") + + @staticmethod + def write_file(file_path: str, content: str, encoding: str = 'utf-8') -> bool: + """ + 写入文件内容 + + Args: + file_path: 文件路径 + content: 文件内容 + encoding: 文件编码 + + Returns: + bool: 写入是否成功 + """ + try: + # 确保目录存在 + FileOperationUtil.ensure_dir(os.path.dirname(file_path)) + + with open(file_path, 'w', encoding=encoding) as file: + file.write(content) + return True + except Exception as e: + print(f"Failed to write file {file_path}: {e}") + return False + + @staticmethod + def read_json(file_path: str) -> Dict[str, Any]: + """ + 读取JSON文件 + + Args: + file_path: JSON文件路径 + + Returns: + Dict[str, Any]: JSON数据 + """ + try: + with open(file_path, 'r', encoding='utf-8') as file: + return json.load(file) + except Exception as e: + raise ValueError(f"Failed to read JSON file {file_path}: {e}") + + @staticmethod + def write_json(file_path: str, data: Dict[str, Any], indent: int = 2) -> bool: + """ + 写入JSON文件 + + Args: + file_path: JSON文件路径 + data: 要写入的数据 + indent: 缩进空格数 + + Returns: + bool: 写入是否成功 + """ + try: + FileOperationUtil.ensure_dir(os.path.dirname(file_path)) + + with open(file_path, 'w', encoding='utf-8') as file: + json.dump(data, file, indent=indent, ensure_ascii=False) + return True + except Exception as e: + print(f"Failed to write JSON file {file_path}: {e}") + return False + + @staticmethod + def read_csv(file_path: str, delimiter: str = ',') -> List[Dict[str, str]]: + """ + 读取CSV文件 + + Args: + file_path: CSV文件路径 + delimiter: 分隔符 + + Returns: + List[Dict[str, str]]: CSV数据列表 + """ + try: + data = [] + with open(file_path, 'r', encoding='utf-8') as file: + reader = csv.DictReader(file, delimiter=delimiter) + data = list(reader) + return data + except Exception as e: + raise ValueError(f"Failed to read CSV file {file_path}: {e}") + + @staticmethod + def write_csv(file_path: str, data: List[Dict[str, Any]], + fieldnames: Optional[List[str]] = None) -> bool: + """ + 写入CSV文件 + + Args: + file_path: CSV文件路径 + data: 要写入的数据 + fieldnames: 字段名列表 + + Returns: + bool: 写入是否成功 + """ + try: + if not data: + return False + + FileOperationUtil.ensure_dir(os.path.dirname(file_path)) + + if fieldnames is None: + fieldnames = list(data[0].keys()) + + with open(file_path, 'w', newline='', encoding='utf-8') as file: + writer = csv.DictWriter(file, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(data) + return True + except Exception as e: + print(f"Failed to write CSV file {file_path}: {e}") + return False + + @staticmethod + def read_yaml(file_path: str) -> Dict[str, Any]: + """ + 读取YAML文件 + + Args: + file_path: YAML文件路径 + + Returns: + Dict[str, Any]: YAML数据 + """ + try: + with open(file_path, 'r', encoding='utf-8') as file: + return yaml.safe_load(file) + except Exception as e: + raise ValueError(f"Failed to read YAML file {file_path}: {e}") + + @staticmethod + def write_yaml(file_path: str, data: Dict[str, Any]) -> bool: + """ + 写入YAML文件 + + Args: + file_path: YAML文件路径 + data: 要写入的数据 + + Returns: + bool: 写入是否成功 + """ + try: + FileOperationUtil.ensure_dir(os.path.dirname(file_path)) + + with open(file_path, 'w', encoding='utf-8') as file: + yaml.dump(data, file, default_flow_style=False, + allow_unicode=True, indent=2) + return True + except Exception as e: + print(f"Failed to write YAML file {file_path}: {e}") + return False + + @staticmethod + def ensure_dir(dir_path: str) -> bool: + """ + 确保目录存在 + + Args: + dir_path: 目录路径 + + Returns: + bool: 操作是否成功 + """ + try: + if dir_path and not os.path.exists(dir_path): + os.makedirs(dir_path, exist_ok=True) + return True + except Exception as e: + print(f"Failed to create directory {dir_path}: {e}") + return False + + @staticmethod + def list_files(dir_path: str, pattern: str = "*", recursive: bool = False) -> List[str]: + """ + 列出目录中的文件 + + Args: + dir_path: 目录路径 + pattern: 文件模式 + recursive: 是否递归搜索 + + Returns: + List[str]: 文件路径列表 + """ + try: + path = Path(dir_path) + if recursive: + return [str(p) for p in path.rglob(pattern) if p.is_file()] + else: + return [str(p) for p in path.glob(pattern) if p.is_file()] + except Exception as e: + print(f"Failed to list files in {dir_path}: {e}") + return [] + + @staticmethod + def get_file_size(file_path: str) -> int: + """ + 获取文件大小 + + Args: + file_path: 文件路径 + + Returns: + int: 文件大小(字节) + """ + try: + return os.path.getsize(file_path) + except Exception: + return 0 + + @staticmethod + def file_exists(file_path: str) -> bool: + """ + 检查文件是否存在 + + Args: + file_path: 文件路径 + + Returns: + bool: 文件是否存在 + """ + return os.path.isfile(file_path) + + @staticmethod + def dir_exists(dir_path: str) -> bool: + """ + 检查目录是否存在 + + Args: + dir_path: 目录路径 + + Returns: + bool: 目录是否存在 + """ + return os.path.isdir(dir_path) +``` + +## 📊 报告生成工具 + +### LocustReportUtil + +Locust报告生成和处理工具。 + +```python +import time +from datetime import datetime +from typing import Dict, List, Any, Optional + +class LocustReportUtil: + """Locust报告工具类""" + + @staticmethod + def generate_html_report(stats_data: Dict[str, Any], + output_path: str = "report.html") -> bool: + """ + 生成HTML报告 + + Args: + stats_data: 统计数据 + output_path: 输出路径 + + Returns: + bool: 生成是否成功 + """ + try: + html_content = LocustReportUtil._create_html_template(stats_data) + return FileOperationUtil.write_file(output_path, html_content) + except Exception as e: + print(f"Failed to generate HTML report: {e}") + return False + + @staticmethod + def generate_csv_report(stats_data: Dict[str, Any], + output_path: str = "report.csv") -> bool: + """ + 生成CSV报告 + + Args: + stats_data: 统计数据 + output_path: 输出路径 + + Returns: + bool: 生成是否成功 + """ + try: + csv_data = LocustReportUtil._convert_to_csv_format(stats_data) + return FileOperationUtil.write_csv(output_path, csv_data) + except Exception as e: + print(f"Failed to generate CSV report: {e}") + return False + + @staticmethod + def generate_json_report(stats_data: Dict[str, Any], + output_path: str = "report.json") -> bool: + """ + 生成JSON报告 + + Args: + stats_data: 统计数据 + output_path: 输出路径 + + Returns: + bool: 生成是否成功 + """ + try: + json_data = LocustReportUtil._format_json_report(stats_data) + return FileOperationUtil.write_json(output_path, json_data) + except Exception as e: + print(f"Failed to generate JSON report: {e}") + return False + + @staticmethod + def parse_locust_stats(stats_file: str) -> Dict[str, Any]: + """ + 解析Locust统计文件 + + Args: + stats_file: 统计文件路径 + + Returns: + Dict[str, Any]: 解析后的统计数据 + """ + try: + if stats_file.endswith('.csv'): + raw_data = FileOperationUtil.read_csv(stats_file) + return LocustReportUtil._process_csv_stats(raw_data) + elif stats_file.endswith('.json'): + return FileOperationUtil.read_json(stats_file) + else: + raise ValueError(f"Unsupported file format: {stats_file}") + except Exception as e: + print(f"Failed to parse stats file {stats_file}: {e}") + return {} + + @staticmethod + def calculate_percentiles(response_times: List[float], + percentiles: List[int] = [50, 90, 95, 99]) -> Dict[str, float]: + """ + 计算响应时间百分位数 + + Args: + response_times: 响应时间列表 + percentiles: 百分位数列表 + + Returns: + Dict[str, float]: 百分位数结果 + """ + if not response_times: + return {f"p{p}": 0.0 for p in percentiles} + + sorted_times = sorted(response_times) + result = {} + + for p in percentiles: + index = int((p / 100.0) * len(sorted_times)) + if index >= len(sorted_times): + index = len(sorted_times) - 1 + result[f"p{p}"] = sorted_times[index] + + return result + + @staticmethod + def format_duration(seconds: float) -> str: + """ + 格式化持续时间 + + Args: + seconds: 秒数 + + Returns: + str: 格式化的时间字符串 + """ + if seconds < 60: + return f"{seconds:.1f}s" + elif seconds < 3600: + minutes = seconds / 60 + return f"{minutes:.1f}m" + else: + hours = seconds / 3600 + return f"{hours:.1f}h" + + @staticmethod + def format_size(bytes_size: int) -> str: + """ + 格式化文件大小 + + Args: + bytes_size: 字节大小 + + Returns: + str: 格式化的大小字符串 + """ + for unit in ['B', 'KB', 'MB', 'GB']: + if bytes_size < 1024.0: + return f"{bytes_size:.1f}{unit}" + bytes_size /= 1024.0 + return f"{bytes_size:.1f}TB" + + @staticmethod + def _create_html_template(stats_data: Dict[str, Any]) -> str: + """创建HTML报告模板""" + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + html = f""" + + + + Locust Performance Test Report + + + + +
+

Performance Test Report

+

Generated at: {timestamp}

+
+ +
+
+

Total Requests

+

{stats_data.get('total_requests', 0)}

+
+
+

Failed Requests

+

{stats_data.get('failed_requests', 0)}

+
+
+

Average Response Time

+

{stats_data.get('avg_response_time', 0):.2f}ms

+
+
+

RPS

+

{stats_data.get('rps', 0):.2f}

+
+
+ + + + + """ + return html + + @staticmethod + def _convert_to_csv_format(stats_data: Dict[str, Any]) -> List[Dict[str, Any]]: + """转换为CSV格式""" + # 实现统计数据到CSV格式的转换 + return [] + + @staticmethod + def _format_json_report(stats_data: Dict[str, Any]) -> Dict[str, Any]: + """格式化JSON报告""" + return { + "timestamp": datetime.now().isoformat(), + "summary": stats_data, + "version": "1.0" + } + + @staticmethod + def _process_csv_stats(raw_data: List[Dict[str, str]]) -> Dict[str, Any]: + """处理CSV统计数据""" + # 实现CSV数据的处理逻辑 + return {} +``` + +## 📝 日志管理工具 + +### LogUtil + +日志配置和管理工具。 + +```python +import logging +import logging.handlers +from typing import Optional + +class LogUtil: + """日志工具类""" + + @staticmethod + def setup_logger(name: str, log_file: Optional[str] = None, + level: int = logging.INFO, + format_string: Optional[str] = None) -> logging.Logger: + """ + 设置日志记录器 + + Args: + name: 日志记录器名称 + log_file: 日志文件路径 + level: 日志级别 + format_string: 日志格式字符串 + + Returns: + logging.Logger: 配置好的日志记录器 + """ + logger = logging.getLogger(name) + logger.setLevel(level) + + # 避免重复添加处理器 + if logger.handlers: + return logger + + # 默认格式 + if format_string is None: + format_string = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + + formatter = logging.Formatter(format_string) + + # 控制台处理器 + console_handler = logging.StreamHandler() + console_handler.setLevel(level) + console_handler.setFormatter(formatter) + logger.addHandler(console_handler) + + # 文件处理器 + if log_file: + FileOperationUtil.ensure_dir(os.path.dirname(log_file)) + file_handler = logging.handlers.RotatingFileHandler( + log_file, maxBytes=10*1024*1024, backupCount=5, encoding='utf-8' + ) + file_handler.setLevel(level) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + return logger + + @staticmethod + def get_logger(name: str) -> logging.Logger: + """ + 获取日志记录器 + + Args: + name: 日志记录器名称 + + Returns: + logging.Logger: 日志记录器 + """ + return logging.getLogger(name) + + @staticmethod + def set_log_level(logger_name: str, level: int) -> None: + """ + 设置日志级别 + + Args: + logger_name: 日志记录器名称 + level: 日志级别 + """ + logger = logging.getLogger(logger_name) + logger.setLevel(level) + + for handler in logger.handlers: + handler.setLevel(level) +``` + +## 🔄 集合点工具 + +### RendezvousUtil + +用户同步和集合点工具。 + +```python +import threading +import time +from typing import Optional + +class RendezvousUtil: + """集合点工具类""" + + def __init__(self, name: str, expected_users: int, timeout: int = 60): + """ + 初始化集合点 + + Args: + name: 集合点名称 + expected_users: 期望的用户数 + timeout: 超时时间(秒) + """ + self.name = name + self.expected_users = expected_users + self.timeout = timeout + self.arrived_users = 0 + self.lock = threading.Lock() + self.condition = threading.Condition(self.lock) + self.start_time = None + + def wait(self, user_id: Optional[str] = None) -> bool: + """ + 等待集合点 + + Args: + user_id: 用户ID + + Returns: + bool: 是否成功通过集合点 + """ + with self.condition: + if self.start_time is None: + self.start_time = time.time() + + self.arrived_users += 1 + current_users = self.arrived_users + + print(f"User {user_id or 'unknown'} arrived at rendezvous '{self.name}' " + f"({current_users}/{self.expected_users})") + + if current_users >= self.expected_users: + # 所有用户都到达,释放所有等待的用户 + self.condition.notify_all() + return True + else: + # 等待其他用户到达 + elapsed = time.time() - self.start_time + remaining_timeout = self.timeout - elapsed + + if remaining_timeout <= 0: + print(f"Rendezvous '{self.name}' timeout") + return False + + return self.condition.wait(timeout=remaining_timeout) + + def reset(self) -> None: + """重置集合点""" + with self.condition: + self.arrived_users = 0 + self.start_time = None + + def get_status(self) -> Dict[str, Any]: + """ + 获取集合点状态 + + Returns: + Dict[str, Any]: 状态信息 + """ + with self.lock: + elapsed = time.time() - self.start_time if self.start_time else 0 + return { + "name": self.name, + "expected_users": self.expected_users, + "arrived_users": self.arrived_users, + "elapsed_time": elapsed, + "timeout": self.timeout, + "is_complete": self.arrived_users >= self.expected_users + } + +# 全局集合点管理器 +class RendezvousManager: + """集合点管理器""" + + def __init__(self): + self._rendezvous = {} + self._lock = threading.Lock() + + def create_rendezvous(self, name: str, expected_users: int, + timeout: int = 60) -> RendezvousUtil: + """ + 创建集合点 + + Args: + name: 集合点名称 + expected_users: 期望用户数 + timeout: 超时时间 + + Returns: + RendezvousUtil: 集合点对象 + """ + with self._lock: + if name in self._rendezvous: + return self._rendezvous[name] + + rendezvous = RendezvousUtil(name, expected_users, timeout) + self._rendezvous[name] = rendezvous + return rendezvous + + def get_rendezvous(self, name: str) -> Optional[RendezvousUtil]: + """ + 获取集合点 + + Args: + name: 集合点名称 + + Returns: + Optional[RendezvousUtil]: 集合点对象 + """ + with self._lock: + return self._rendezvous.get(name) + + def remove_rendezvous(self, name: str) -> bool: + """ + 移除集合点 + + Args: + name: 集合点名称 + + Returns: + bool: 移除是否成功 + """ + with self._lock: + if name in self._rendezvous: + del self._rendezvous[name] + return True + return False + +# 全局实例 +rendezvous_manager = RendezvousManager() +``` + +## 🛠️ 通用工具函数 + +### CommonUtil + +通用工具函数集合。 + +```python +import random +import string +import hashlib +import uuid +from typing import Any, List, Dict + +class CommonUtil: + """通用工具类""" + + @staticmethod + def generate_random_string(length: int = 10, + chars: str = string.ascii_letters + string.digits) -> str: + """ + 生成随机字符串 + + Args: + length: 字符串长度 + chars: 字符集 + + Returns: + str: 随机字符串 + """ + return ''.join(random.choice(chars) for _ in range(length)) + + @staticmethod + def generate_uuid() -> str: + """ + 生成UUID + + Returns: + str: UUID字符串 + """ + return str(uuid.uuid4()) + + @staticmethod + def calculate_md5(text: str) -> str: + """ + 计算MD5哈希值 + + Args: + text: 输入文本 + + Returns: + str: MD5哈希值 + """ + return hashlib.md5(text.encode('utf-8')).hexdigest() + + @staticmethod + def safe_get(data: Dict[str, Any], key: str, default: Any = None) -> Any: + """ + 安全获取字典值 + + Args: + data: 字典 + key: 键名 + default: 默认值 + + Returns: + Any: 字典值或默认值 + """ + return data.get(key, default) + + @staticmethod + def retry_on_exception(func, max_retries: int = 3, delay: float = 1.0): + """ + 异常重试装饰器 + + Args: + func: 要重试的函数 + max_retries: 最大重试次数 + delay: 重试延迟 + + Returns: + Any: 函数返回值 + """ + for attempt in range(max_retries + 1): + try: + return func() + except Exception as e: + if attempt == max_retries: + raise e + time.sleep(delay) + + @staticmethod + def format_number(number: float, precision: int = 2) -> str: + """ + 格式化数字 + + Args: + number: 数字 + precision: 精度 + + Returns: + str: 格式化的数字字符串 + """ + return f"{number:.{precision}f}" + + @staticmethod + def merge_dicts(*dicts: Dict[str, Any]) -> Dict[str, Any]: + """ + 合并多个字典 + + Args: + *dicts: 字典列表 + + Returns: + Dict[str, Any]: 合并后的字典 + """ + result = {} + for d in dicts: + if d: + result.update(d) + return result +``` + +## 📚 使用示例 + +### 综合使用示例 + +```python +from src.utils.file_operation import FileOperationUtil +from src.utils.locust_report import LocustReportUtil +from src.utils.log_moudle import LogUtil +from src.utils.rendezvous import rendezvous_manager +from src.utils.util import CommonUtil + +# 设置日志 +logger = LogUtil.setup_logger("test_logger", "logs/test.log") + +# 读取配置文件 +config = FileOperationUtil.read_yaml("config/test_config.yaml") + +# 创建集合点 +rendezvous = rendezvous_manager.create_rendezvous("login_sync", 10, 30) + +# 生成测试数据 +test_id = CommonUtil.generate_uuid() +random_data = CommonUtil.generate_random_string(20) + +# 生成报告 +stats_data = {"total_requests": 1000, "failed_requests": 5} +LocustReportUtil.generate_html_report(stats_data, "reports/test_report.html") + +logger.info(f"Test {test_id} completed with data: {random_data}") +``` + +## 📚 相关文档 + +- [文件操作示例](../examples/basic-examples.md) - 文件操作使用示例 +- [报告生成指南](../examples/advanced-usage.md) - 报告生成详细说明 +- [日志配置](../configuration/framework-config.md) - 日志系统配置 +- [开发指南](../development/setup.md) - 开发环境搭建 diff --git a/docs/architecture/data-flow.md b/docs/architecture/data-flow.md new file mode 100644 index 0000000..6cc4ef9 --- /dev/null +++ b/docs/architecture/data-flow.md @@ -0,0 +1,407 @@ +# 数据流架构 + +本文档详细介绍Locust性能测试框架中数据的处理和流转机制。 + +## 🎯 数据流概览 + +框架中的数据流可以分为以下几个主要阶段: + +``` +数据输入 → 数据处理 → 数据分析 → 数据输出 + ↓ ↓ ↓ ↓ +测试数据 性能指标 分析结果 报告生成 + ↓ ↓ ↓ ↓ +数据源 实时监控 趋势分析 通知告警 +``` + +## 📊 数据类型分类 + +### 1. 输入数据 (Input Data) + +#### 测试配置数据 +- **来源**: 配置文件、命令行参数、环境变量 +- **格式**: YAML、JSON、命令行参数 +- **内容**: 测试参数、用户配置、负载设置 + +```yaml +# 示例配置数据 +test_config: + users: 100 + spawn_rate: 10 + run_time: "5m" + host: "https://api.example.com" +``` + +#### 测试数据 +- **来源**: CSV文件、数据库、API接口、数据生成器 +- **格式**: CSV、JSON、XML、数据库记录 +- **内容**: 用户数据、业务数据、参数化数据 + +```csv +# 示例CSV数据 +username,password,user_id +user1,pass1,1001 +user2,pass2,1002 +user3,pass3,1003 +``` + +#### 脚本数据 +- **来源**: Python测试脚本 +- **格式**: Python代码 +- **内容**: 测试逻辑、业务流程、断言规则 + +### 2. 运行时数据 (Runtime Data) + +#### 性能指标数据 +- **响应时间**: 请求响应时间统计 +- **吞吐量**: TPS、RPS等吞吐量指标 +- **错误率**: 错误请求统计和分类 +- **并发数**: 实时用户数和连接数 + +```json +{ + "timestamp": "2024-01-01T10:00:00Z", + "response_time": 150.5, + "status_code": 200, + "request_name": "GET /api/users", + "user_id": "user_001", + "error": null +} +``` + +#### 系统监控数据 +- **CPU使用率**: 系统和进程CPU占用 +- **内存使用**: 内存占用和垃圾回收 +- **网络IO**: 网络带宽和连接状态 +- **磁盘IO**: 磁盘读写性能 + +#### 事件数据 +- **测试事件**: 测试开始、结束、用户变化 +- **系统事件**: 错误、警告、状态变化 +- **用户事件**: 用户行为、操作记录 + +### 3. 输出数据 (Output Data) + +#### 分析结果 +- **性能评估**: A-D等级评分 +- **趋势分析**: 性能趋势和预测 +- **瓶颈识别**: 性能瓶颈定位 +- **建议报告**: 优化建议和改进方案 + +#### 报告数据 +- **HTML报告**: 可视化性能报告 +- **CSV数据**: 原始数据导出 +- **JSON格式**: API集成数据 +- **图表数据**: 图表和可视化数据 + +## 🔄 数据流转过程 + +### 1. 数据采集阶段 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 配置文件 │ │ 测试脚本 │ │ 数据源 │ +└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ + └──────────────────┼──────────────────┘ + │ + ┌──────▼──────┐ + │ 数据采集器 │ + └──────┬──────┘ + │ + ┌──────▼──────┐ + │ 数据验证器 │ + └──────┬──────┘ + │ + ┌──────▼──────┐ + │ 数据缓存 │ + └─────────────┘ +``` + +**主要组件**: +- **配置加载器**: 加载和解析配置文件 +- **数据源连接器**: 连接各种数据源 +- **数据验证器**: 验证数据格式和完整性 +- **数据缓存**: 缓存常用数据提高性能 + +### 2. 数据处理阶段 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 原始数据 │ │ 数据清洗 │ │ 数据转换 │ +└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ + └──────────────────┼──────────────────┘ + │ + ┌──────▼──────┐ + │ 数据处理器 │ + └──────┬──────┘ + │ + ┌──────▼──────┐ + │ 数据聚合器 │ + └──────┬──────┘ + │ + ┌──────▼──────┐ + │ 处理结果 │ + └─────────────┘ +``` + +**处理步骤**: +1. **数据清洗**: 去除无效数据、处理异常值 +2. **数据转换**: 格式转换、单位统一 +3. **数据聚合**: 统计计算、指标汇总 +4. **数据标准化**: 统一数据格式和结构 + +### 3. 数据分析阶段 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 性能指标 │ │ 统计分析 │ │ 趋势分析 │ +└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ + └──────────────────┼──────────────────┘ + │ + ┌──────▼──────┐ + │ 分析引擎 │ + └──────┬──────┘ + │ + ┌──────▼──────┐ + │ 评估算法 │ + └──────┬──────┘ + │ + ┌──────▼──────┐ + │ 分析结果 │ + └─────────────┘ +``` + +**分析维度**: +- **响应时间分析**: P50/P90/P95/P99百分位数 +- **吞吐量分析**: TPS趋势和峰值分析 +- **错误率分析**: 错误分布和根因分析 +- **稳定性分析**: 性能波动和稳定性评估 + +### 4. 数据输出阶段 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 分析结果 │ │ 报告生成 │ │ 数据导出 │ +└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ + └──────────────────┼──────────────────┘ + │ + ┌──────▼──────┐ + │ 输出管理器 │ + └──────┬──────┘ + │ + ┌──────▼──────┐ + │ 格式转换器 │ + └──────┬──────┘ + │ + ┌──────▼──────┐ + │ 最终输出 │ + └─────────────┘ +``` + +**输出格式**: +- **HTML报告**: 交互式Web报告 +- **PDF报告**: 可打印的PDF文档 +- **CSV数据**: 原始数据表格 +- **JSON API**: 程序化接口数据 + +## 🔧 数据管理组件 + +### 1. 数据提供器 (DataProvider) + +**职责**: 统一的数据访问接口 + +```python +class DataProvider: + def get_test_data(self, data_type, filters=None): + """获取测试数据""" + pass + + def get_config_data(self, config_key): + """获取配置数据""" + pass + + def get_runtime_data(self, metric_name, time_range): + """获取运行时数据""" + pass +``` + +### 2. 数据生成器 (DataGenerator) + +**职责**: 动态生成测试数据 + +```python +class DataGenerator: + def generate_user_data(self, count, pattern): + """生成用户数据""" + pass + + def generate_business_data(self, schema, count): + """生成业务数据""" + pass + + def generate_random_data(self, data_type, constraints): + """生成随机数据""" + pass +``` + +### 3. 数据分发器 (DataDistributor) + +**职责**: 在分布式环境中分发数据 + +```python +class DataDistributor: + def distribute_data(self, data, strategy, nodes): + """分发数据到各节点""" + pass + + def sync_data(self, source_node, target_nodes): + """同步数据""" + pass + + def balance_load(self, data_load, available_nodes): + """负载均衡""" + pass +``` + +### 4. 数据存储器 (DataStorage) + +**职责**: 数据持久化和检索 + +```python +class DataStorage: + def store_metrics(self, metrics_data): + """存储性能指标""" + pass + + def store_results(self, test_results): + """存储测试结果""" + pass + + def retrieve_history(self, query_params): + """检索历史数据""" + pass +``` + +## 📈 实时数据流 + +### 1. 实时监控流 + +``` +测试执行 → 指标收集 → 实时分析 → 告警检查 → 通知发送 + ↓ ↓ ↓ ↓ ↓ + 事件流 指标流 分析流 告警流 通知流 +``` + +### 2. 数据流管道 + +```python +# 数据流管道示例 +class DataPipeline: + def __init__(self): + self.collectors = [] + self.processors = [] + self.analyzers = [] + self.outputs = [] + + def add_collector(self, collector): + self.collectors.append(collector) + + def add_processor(self, processor): + self.processors.append(processor) + + def process_data(self, raw_data): + # 数据收集 + collected_data = self._collect_data(raw_data) + + # 数据处理 + processed_data = self._process_data(collected_data) + + # 数据分析 + analyzed_data = self._analyze_data(processed_data) + + # 数据输出 + return self._output_data(analyzed_data) +``` + +## 🔄 数据同步机制 + +### 1. 分布式数据同步 + +在分布式测试环境中,需要确保各节点间的数据一致性: + +```python +class DataSynchronizer: + def sync_test_data(self, master_node, worker_nodes): + """同步测试数据""" + pass + + def sync_config(self, config_data, target_nodes): + """同步配置数据""" + pass + + def collect_results(self, worker_nodes): + """收集各节点结果""" + pass +``` + +### 2. 数据一致性保证 + +- **版本控制**: 数据版本管理和冲突解决 +- **校验机制**: 数据完整性和一致性校验 +- **恢复机制**: 数据丢失和损坏恢复 + +## 📊 数据质量保证 + +### 1. 数据验证 + +```python +class DataValidator: + def validate_format(self, data, schema): + """验证数据格式""" + pass + + def validate_range(self, value, min_val, max_val): + """验证数值范围""" + pass + + def validate_completeness(self, data, required_fields): + """验证数据完整性""" + pass +``` + +### 2. 数据清洗 + +- **异常值处理**: 识别和处理异常数据 +- **缺失值填充**: 合理填充缺失数据 +- **重复数据去除**: 识别和去除重复记录 + +## 🚀 性能优化 + +### 1. 数据缓存策略 + +- **内存缓存**: 热点数据内存缓存 +- **分层缓存**: 多级缓存机制 +- **缓存更新**: 智能缓存更新策略 + +### 2. 数据压缩 + +- **传输压缩**: 网络传输数据压缩 +- **存储压缩**: 磁盘存储数据压缩 +- **实时压缩**: 实时数据流压缩 + +### 3. 并行处理 + +- **数据分片**: 大数据集分片处理 +- **并行计算**: 多线程/多进程并行 +- **流式处理**: 流式数据处理 + +## 📚 相关文档 + +- [模块设计](modules.md) - 各模块详细设计 +- [插件系统](plugin-system.md) - 插件架构说明 +- [数据管理API](../api/data-manager.md) - 数据管理API文档 +- [配置参考](../configuration/framework-config.md) - 数据相关配置 diff --git a/docs/architecture/modules.md b/docs/architecture/modules.md new file mode 100644 index 0000000..5649a58 --- /dev/null +++ b/docs/architecture/modules.md @@ -0,0 +1,238 @@ +# 模块设计 + +本文档详细介绍Locust性能测试框架的各个模块设计和实现。 + +## 📋 模块概览 + +框架采用分层架构设计,主要包含以下核心模块: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 用户接口层 (UI Layer) │ +├─────────────────────────────────────────────────────────────┤ +│ 业务逻辑层 (Business Layer) │ +├─────────────────────────────────────────────────────────────┤ +│ 数据访问层 (Data Access Layer) │ +├─────────────────────────────────────────────────────────────┤ +│ 基础设施层 (Infrastructure Layer) │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 🎯 核心模块 + +### 1. 测试执行模块 (src/model/) + +**职责**: 负责测试脚本的执行和管理 + +**主要组件**: +- `locust_test.py` - Locust测试执行器 +- `modelsbase.py` - 基础模型类 +- `auto_pytest.py` - 自动化测试集成 + +**核心功能**: +- 测试脚本加载和执行 +- 用户行为模拟 +- 事务管理和统计 +- 集合点同步机制 + +### 2. 性能分析模块 (src/analysis/) + +**职责**: 提供全面的性能数据分析和评估 + +**主要组件**: +- `performance_analyzer.py` - 性能分析器 +- `metrics_calculator.py` - 指标计算器 +- `report_generator.py` - 报告生成器 +- `trend_analyzer.py` - 趋势分析器 + +**核心功能**: +- 响应时间分析 (P50/P90/P95/P99) +- 吞吐量计算和分析 +- 错误率统计和分析 +- 性能等级评估 (A-D等级) +- 趋势分析和预测 + +### 3. 监控告警模块 (src/monitoring/) + +**职责**: 实时监控系统状态并提供告警机制 + +**主要组件**: +- `system_monitor.py` - 系统监控器 +- `alert_manager.py` - 告警管理器 +- `threshold_checker.py` - 阈值检查器 +- `metric_collector.py` - 指标收集器 + +**核心功能**: +- 实时性能指标监控 +- 系统资源监控 (CPU、内存、网络) +- 可配置的告警规则 +- 多级告警机制 (info/warning/critical) +- 告警生命周期管理 + +### 4. 数据管理模块 (src/data_manager/) + +**职责**: 管理测试数据的生成、分发和同步 + +**主要组件**: +- `data_provider.py` - 数据提供器 +- `data_generator.py` - 数据生成器 +- `data_distributor.py` - 数据分发器 +- `data_synchronizer.py` - 数据同步器 + +**核心功能**: +- 基于Faker的测试数据生成 +- 多种数据分发策略 (轮询、随机、顺序) +- 分布式环境数据同步 +- CSV/数据库/API数据源支持 +- 动态数据更新和管理 + +### 5. 通知服务模块 (src/notifications/) + +**职责**: 提供多渠道消息通知服务 + +**主要组件**: +- `notification_service.py` - 通知服务 +- `feishu_notifier.py` - 飞书通知器 +- `dingtalk_notifier.py` - 钉钉通知器 +- `email_notifier.py` - 邮件通知器 +- `wechat_notifier.py` - 企业微信通知器 + +**核心功能**: +- 多渠道消息发送 +- 消息模板管理 +- 发送状态跟踪 +- 失败重试机制 +- 消息格式化和富文本支持 + +### 6. 插件系统模块 (src/plugins/) + +**职责**: 提供可扩展的插件架构 + +**主要组件**: +- `plugin_manager.py` - 插件管理器 +- `base_plugin.py` - 插件基类 +- `plugin_loader.py` - 插件加载器 +- `plugin_registry.py` - 插件注册表 + +**核心功能**: +- 插件生命周期管理 +- 动态插件加载/卸载 +- 插件依赖管理 +- 事件驱动的插件通信 +- 插件配置管理 + +## 🔧 工具模块 + +### 1. 工具类模块 (src/utils/) + +**职责**: 提供通用的工具函数和辅助类 + +**主要组件**: +- `file_operation.py` - 文件操作工具 +- `locust_report.py` - 报告生成工具 +- `log_moudle.py` - 日志管理工具 +- `rendezvous.py` - 集合点实现 +- `robot.py` - 自动化工具 +- `util.py` - 通用工具函数 + +### 2. 客户端模块 (src/client/) + +**职责**: 提供HTTP客户端和认证支持 + +**主要组件**: +- `demo_client/` - 示例客户端实现 +- `flask_auth.py` - Flask认证支持 +- `flask_client.py` - Flask客户端 +- `response.py` - 响应处理 + +## 🏗️ 模块间交互 + +### 数据流向 + +``` +测试执行模块 → 性能分析模块 → 报告生成 + ↓ ↓ +监控告警模块 → 通知服务模块 + ↓ +数据管理模块 ← 插件系统模块 +``` + +### 依赖关系 + +- **核心依赖**: 测试执行模块是整个系统的核心 +- **数据依赖**: 性能分析模块依赖测试执行模块的数据 +- **服务依赖**: 监控告警模块依赖通知服务模块 +- **扩展依赖**: 所有模块都可以通过插件系统进行扩展 + +## 📊 模块配置 + +每个模块都支持独立的配置管理: + +```yaml +# conf/settings.yaml +modules: + analysis: + enabled: true + config_file: "analysis_config.yaml" + + monitoring: + enabled: true + config_file: "monitoring_config.yaml" + + notifications: + enabled: true + config_file: "notification_config.yaml" + + data_manager: + enabled: true + config_file: "data_config.yaml" + + plugins: + enabled: true + config_file: "plugin_config.yaml" +``` + +## 🔄 模块生命周期 + +### 初始化阶段 +1. 配置加载 +2. 模块注册 +3. 依赖检查 +4. 资源分配 + +### 运行阶段 +1. 模块启动 +2. 服务注册 +3. 事件监听 +4. 数据处理 + +### 清理阶段 +1. 资源释放 +2. 连接关闭 +3. 数据保存 +4. 模块卸载 + +## 🚀 扩展指南 + +### 添加新模块 + +1. **创建模块目录**: 在`src/`下创建新的模块目录 +2. **实现模块接口**: 继承基础模块类 +3. **注册模块**: 在模块注册表中注册新模块 +4. **配置模块**: 添加模块配置文件 +5. **编写测试**: 为新模块编写单元测试 + +### 模块开发规范 + +- 遵循单一职责原则 +- 保持模块间的松耦合 +- 使用依赖注入 +- 提供清晰的接口定义 +- 支持配置化管理 + +## 📚 相关文档 + +- [整体架构](overview.md) - 框架整体架构设计 +- [插件系统](plugin-system.md) - 插件架构详细说明 +- [数据流](data-flow.md) - 数据处理流程 +- [开发指南](../development/setup.md) - 开发环境搭建 diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md new file mode 100644 index 0000000..91d7f28 --- /dev/null +++ b/docs/architecture/overview.md @@ -0,0 +1,567 @@ +# 整体架构设计 + +本文档详细介绍Locust性能测试框架的整体架构设计,包括设计原则、架构模式和核心组件。 + +## 🎯 设计原则 + +### 1. 核心设计原则 + +#### 分层架构 (Layered Architecture) +- **表现层**: Web UI、CLI接口、API接口 +- **业务层**: 测试逻辑、数据处理、业务规则 +- **服务层**: 分析服务、监控服务、通知服务 +- **数据层**: 数据访问、存储管理、缓存机制 +- **基础层**: 日志、配置、工具、框架支持 + +#### 松耦合 (Loose Coupling) +- 模块间通过接口通信,减少直接依赖 +- 使用事件驱动机制实现模块解耦 +- 依赖注入和控制反转降低耦合度 + +#### 高内聚 (High Cohesion) +- 相关功能集中在同一模块内 +- 模块内部功能紧密相关,职责明确 +- 单一职责原则确保模块专注性 + +#### 可扩展性 (Extensibility) +- 插件化架构支持功能扩展 +- 开闭原则:对扩展开放,对修改封闭 +- 标准化接口便于第三方集成 + +## 🏗️ 架构概览 + +### 1. 整体架构图 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 用户接口层 │ +├─────────────────┬─────────────────┬─────────────────────────────┤ +│ Web UI │ CLI Interface │ REST API │ +│ (Locust Web) │ (命令行工具) │ (外部集成) │ +└─────────────────┴─────────────────┴─────────────────────────────┘ + │ +┌─────────────────────────────────────────────────────────────────┐ +│ 业务逻辑层 │ +├─────────────────┬─────────────────┬─────────────────────────────┤ +│ 测试编排 │ 负载控制 │ 结果处理 │ +│ (Test Orchestration) │ (Load Control) │ (Result Processing) │ +└─────────────────┴─────────────────┴─────────────────────────────┘ + │ +┌─────────────────────────────────────────────────────────────────┐ +│ 服务层 │ +├─────────┬─────────┬─────────┬─────────┬─────────┬─────────────┤ +│ 性能分析 │ 实时监控 │ 数据管理 │ 插件系统 │ 通知服务 │ 报告生成 │ +│ Analysis│ Monitor │ Data Mgr│ Plugins │ Notify │ Reporting │ +└─────────┴─────────┴─────────┴─────────┴─────────┴─────────────┘ + │ +┌─────────────────────────────────────────────────────────────────┐ +│ 数据访问层 │ +├─────────────────┬─────────────────┬─────────────────────────────┤ +│ 数据库访问 │ 文件系统 │ 缓存系统 │ +│ (Database) │ (File System) │ (Cache) │ +└─────────────────┴─────────────────┴─────────────────────────────┘ + │ +┌─────────────────────────────────────────────────────────────────┐ +│ 基础设施层 │ +├─────────┬─────────┬─────────┬─────────┬─────────┬─────────────┤ +│ 日志系统 │ 配置管理 │ 工具类 │ 异常处理 │ 安全认证 │ 网络通信 │ +│ Logging │ Config │ Utils │ Exception│ Security│ Network │ +└─────────┴─────────┴─────────┴─────────┴─────────┴─────────────┘ +``` + +### 2. 核心组件关系 + +```mermaid +graph TB + A[Locust Core] --> B[Test Execution] + A --> C[Load Generation] + + B --> D[Performance Analysis] + B --> E[Real-time Monitoring] + B --> F[Result Collection] + + C --> G[Load Shapes] + C --> H[User Simulation] + C --> I[Data Management] + + D --> J[Report Generation] + E --> K[Alert Management] + F --> L[Data Storage] + + M[Plugin System] --> D + M --> E + M --> G + M --> J + M --> K + + N[Configuration] --> A + N --> M + + O[Logging] --> A + O --> M +``` + +## 🔧 核心组件架构 + +### 1. 性能分析组件 (Analysis) + +```python +# 组件结构 +src/analysis/ +├── performance_analyzer.py # 核心分析引擎 +├── trend_analyzer.py # 趋势分析 +├── report_generator.py # 报告生成 +├── metrics_calculator.py # 指标计算 +└── __init__.py + +# 架构特点 +- 单一职责:专注性能数据分析 +- 可扩展:支持自定义分析算法 +- 高性能:优化的计算引擎 +- 多格式:支持多种输出格式 +``` + +#### 分析流水线 +``` +原始数据 → 数据清洗 → 指标计算 → 趋势分析 → 评分评级 → 报告生成 +``` + +### 2. 监控告警组件 (Monitoring) + +```python +# 组件结构 +src/monitoring/ +├── performance_monitor.py # 性能监控 +├── alert_manager.py # 告警管理 +├── notification_service.py # 通知服务 +├── metrics_collector.py # 指标收集 +└── __init__.py + +# 架构特点 +- 实时性:毫秒级监控响应 +- 可配置:灵活的告警规则 +- 多渠道:支持多种通知方式 +- 高可用:容错和恢复机制 +``` + +#### 监控流程 +``` +指标收集 → 规则评估 → 告警触发 → 通知发送 → 状态管理 +``` + +### 3. 数据管理组件 (Data Manager) + +```python +# 组件结构 +src/data_manager/ +├── data_generator.py # 数据生成 +├── data_provider.py # 数据提供 +├── data_distributor.py # 数据分发 +├── data_storage.py # 数据存储 +└── __init__.py + +# 架构特点 +- 智能生成:基于Faker的真实数据 +- 多策略:支持多种分发策略 +- 分布式:支持集群数据同步 +- 高性能:优化的数据访问 +``` + +#### 数据流转 +``` +数据生成 → 数据验证 → 数据存储 → 数据分发 → 数据消费 +``` + +### 4. 插件系统组件 (Plugins) + +```python +# 组件结构 +src/plugins/ +├── plugin_interface.py # 插件接口定义 +├── plugin_manager.py # 插件管理器 +├── plugin_loader.py # 插件加载器 +├── builtin/ # 内置插件 +│ └── csv_report_plugin.py +└── __init__.py + +# 架构特点 +- 标准化:统一的插件接口 +- 动态加载:运行时插件管理 +- 类型丰富:支持10+种插件类型 +- 生命周期:完整的插件生命周期管理 +``` + +#### 插件生命周期 +``` +发现 → 加载 → 验证 → 初始化 → 启用 → 运行 → 禁用 → 卸载 +``` + +## 🔄 数据流架构 + +### 1. 测试数据流 + +```mermaid +sequenceDiagram + participant U as User + participant L as Locust Core + participant DM as Data Manager + participant A as Analysis + participant M as Monitor + participant R as Report + + U->>L: 启动测试 + L->>DM: 请求测试数据 + DM-->>L: 返回测试数据 + L->>L: 执行测试 + L->>M: 发送实时指标 + L->>A: 发送测试结果 + M->>M: 监控分析 + A->>A: 性能分析 + A->>R: 生成报告 + R-->>U: 返回报告 +``` + +### 2. 监控数据流 + +```mermaid +graph LR + A[指标收集] --> B[数据缓存] + B --> C[规则引擎] + C --> D{是否触发告警} + D -->|是| E[告警管理] + D -->|否| F[继续监控] + E --> G[通知发送] + G --> H[状态更新] + F --> A + H --> A +``` + +## 🔌 插件架构设计 + +### 1. 插件接口层次 + +```python +# 基础插件接口 +class PluginInterface(ABC): + @property + @abstractmethod + def plugin_info(self) -> PluginInfo: + pass + + @abstractmethod + def initialize(self, config=None) -> bool: + pass + + @abstractmethod + def cleanup(self): + pass + +# 专用插件接口 +class ReportPlugin(PluginInterface): + @abstractmethod + def generate_report(self, test_data, output_path): + pass + +class MonitorPlugin(PluginInterface): + @abstractmethod + def collect_metrics(self) -> Dict[str, Any]: + pass +``` + +### 2. 插件管理架构 + +```python +# 插件管理器架构 +class PluginManager: + def __init__(self): + self.loader = PluginLoader() + self.plugins = {} + self.event_handlers = {} + + # 插件生命周期管理 + def discover_plugins(self) -> List[str] + def load_plugin(self, name: str) -> bool + def enable_plugin(self, name: str, config=None) -> bool + def disable_plugin(self, name: str) -> bool + + # 事件系统 + def register_event_handler(self, event: str, handler) + def trigger_event(self, event: str, data=None) +``` + +## 🏛️ 分布式架构 + +### 1. Master-Worker模式 + +```python +# Master节点职责 +- 测试协调和控制 +- 负载分发和调度 +- 结果收集和聚合 +- Web UI和API服务 + +# Worker节点职责 +- 负载生成和执行 +- 本地指标收集 +- 结果上报 +- 状态同步 +``` + +### 2. 分布式通信 + +```mermaid +graph TB + M[Master Node] --> W1[Worker Node 1] + M --> W2[Worker Node 2] + M --> W3[Worker Node 3] + + W1 --> M + W2 --> M + W3 --> M + + M --> DB[(Database)] + M --> Cache[(Cache)] + M --> MQ[(Message Queue)] +``` + +## 🔒 安全架构 + +### 1. 安全层次 + +```python +# 认证层 +- 用户身份验证 +- API密钥管理 +- 会话管理 + +# 授权层 +- 角色权限控制 +- 资源访问控制 +- 操作权限验证 + +# 数据安全层 +- 数据加密存储 +- 传输加密 +- 敏感信息脱敏 +``` + +### 2. 安全机制 + +```python +# 配置安全 +- 敏感配置加密 +- 环境变量隔离 +- 配置文件权限控制 + +# 网络安全 +- HTTPS通信 +- 防火墙配置 +- 网络隔离 + +# 运行时安全 +- 输入验证 +- SQL注入防护 +- XSS防护 +``` + +## 📊 性能架构 + +### 1. 性能优化策略 + +```python +# 计算性能 +- 多线程并发处理 +- 异步IO操作 +- 内存池管理 +- 算法优化 + +# 存储性能 +- 数据库连接池 +- 查询优化 +- 索引设计 +- 缓存策略 + +# 网络性能 +- 连接复用 +- 压缩传输 +- 负载均衡 +- CDN加速 +``` + +### 2. 可扩展性设计 + +```python +# 水平扩展 +- 分布式部署 +- 负载均衡 +- 数据分片 +- 服务拆分 + +# 垂直扩展 +- 资源优化 +- 性能调优 +- 硬件升级 +- 算法改进 +``` + +## 🔧 配置架构 + +### 1. 配置层次结构 + +```python +# 配置优先级 +环境变量 > 命令行参数 > 用户配置 > 默认配置 + +# 配置文件结构 +conf/ +├── settings.toml # 主配置文件 +├── environments/ # 环境配置 +│ ├── development.toml +│ ├── testing.toml +│ └── production.toml +├── plugins/ # 插件配置 +│ └── plugin_config.json +└── logging.conf # 日志配置 +``` + +### 2. 配置管理机制 + +```python +# 动态配置 +- 运行时配置更新 +- 配置热重载 +- 配置验证 +- 默认值处理 + +# 配置安全 +- 敏感信息加密 +- 配置文件权限 +- 环境隔离 +- 审计日志 +``` + +## 🚀 部署架构 + +### 1. 部署模式 + +```python +# 单机部署 +- 开发测试环境 +- 小规模测试 +- 快速验证 + +# 分布式部署 +- 大规模测试 +- 高可用要求 +- 性能要求高 + +# 云原生部署 +- 容器化部署 +- Kubernetes编排 +- 弹性伸缩 +- 服务网格 +``` + +### 2. 容器化架构 + +```dockerfile +# 多阶段构建 +FROM python:3.9-slim as builder +# 构建阶段 + +FROM python:3.9-slim as runtime +# 运行阶段 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8089/health || exit 1 +``` + +## 📈 监控架构 + +### 1. 监控层次 + +```python +# 应用监控 +- 性能指标监控 +- 业务指标监控 +- 错误监控 +- 日志监控 + +# 系统监控 +- 资源使用监控 +- 网络监控 +- 存储监控 +- 服务健康监控 + +# 基础设施监控 +- 服务器监控 +- 数据库监控 +- 中间件监控 +- 网络设备监控 +``` + +### 2. 监控技术栈 + +```python +# 指标收集 +- Prometheus +- Grafana +- InfluxDB +- StatsD + +# 日志收集 +- ELK Stack +- Fluentd +- Logstash +- Filebeat + +# 链路追踪 +- Jaeger +- Zipkin +- SkyWalking +- OpenTelemetry +``` + +## 🎯 架构演进 + +### 1. 版本演进路线 + +```python +# v1.0 - 基础版本 +- 核心测试功能 +- 基础报告生成 +- 简单配置管理 + +# v2.0 - 增强版本 (当前) +- 性能分析模块 +- 监控告警系统 +- 插件化架构 +- 数据管理系统 + +# v3.0 - 未来版本 +- AI驱动的性能分析 +- 云原生架构 +- 微服务化 +- 智能化运维 +``` + +### 2. 技术债务管理 + +```python +# 代码质量 +- 代码审查 +- 单元测试 +- 集成测试 +- 性能测试 + +# 架构优化 +- 模块重构 +- 性能优化 +- 安全加固 +- 文档完善 +``` + +--- + +这个架构设计确保了框架的可扩展性、可维护性和高性能,为用户提供了强大而灵活的性能测试解决方案。 diff --git a/docs/architecture/plugin-system.md b/docs/architecture/plugin-system.md new file mode 100644 index 0000000..50da144 --- /dev/null +++ b/docs/architecture/plugin-system.md @@ -0,0 +1,440 @@ +# 插件系统架构 + +本文档详细介绍Locust性能测试框架的插件系统架构设计和扩展机制。 + +## 🎯 设计目标 + +插件系统旨在提供: +- **可扩展性**: 支持功能的动态扩展 +- **模块化**: 插件间相互独立,可单独开发和部署 +- **热插拔**: 运行时动态加载和卸载插件 +- **标准化**: 统一的插件开发接口和规范 +- **安全性**: 插件隔离和权限控制 + +## 🏗️ 架构概览 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 插件管理器 (Plugin Manager) │ +├─────────────────────────────────────────────────────────────┤ +│ 插件加载器 │ 插件注册表 │ 事件总线 │ 生命周期管理器 │ +├─────────────────────────────────────────────────────────────┤ +│ 插件接口层 (Plugin Interface) │ +├─────────────────────────────────────────────────────────────┤ +│ Locust插件 │ 报告插件 │ 监控插件 │ 数据插件 │ ... │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 🔌 插件类型 + +### 1. Locust插件 (LocustPlugin) + +**用途**: 扩展Locust核心功能 + +**接口方法**: +```python +class LocustPlugin(BasePlugin): + def on_test_start(self, environment, **kwargs): + """测试开始时调用""" + pass + + def on_test_stop(self, environment, **kwargs): + """测试结束时调用""" + pass + + def on_user_add(self, user_instance, **kwargs): + """用户添加时调用""" + pass + + def on_user_remove(self, user_instance, **kwargs): + """用户移除时调用""" + pass +``` + +**应用场景**: +- 自定义用户行为 +- 测试数据预处理 +- 自定义统计指标 +- 测试流程控制 + +### 2. 报告插件 (ReportPlugin) + +**用途**: 自定义报告格式和内容 + +**接口方法**: +```python +class ReportPlugin(BasePlugin): + def generate_report(self, stats, **kwargs): + """生成自定义报告""" + pass + + def format_data(self, data, format_type): + """格式化数据""" + pass + + def export_report(self, report, output_path): + """导出报告""" + pass +``` + +**应用场景**: +- 自定义报告模板 +- 特殊格式导出 +- 报告数据处理 +- 第三方系统集成 + +### 3. 监控插件 (MonitorPlugin) + +**用途**: 扩展监控能力 + +**接口方法**: +```python +class MonitorPlugin(BasePlugin): + def collect_metrics(self): + """收集监控指标""" + pass + + def check_thresholds(self, metrics): + """检查阈值""" + pass + + def trigger_alert(self, alert_data): + """触发告警""" + pass +``` + +**应用场景**: +- 自定义监控指标 +- 第三方监控系统集成 +- 特殊告警规则 +- 性能数据收集 + +### 4. 数据插件 (DataPlugin) + +**用途**: 扩展数据源和数据处理能力 + +**接口方法**: +```python +class DataPlugin(BasePlugin): + def load_data(self, source_config): + """加载数据""" + pass + + def process_data(self, raw_data): + """处理数据""" + pass + + def distribute_data(self, data, strategy): + """分发数据""" + pass +``` + +**应用场景**: +- 自定义数据源 +- 数据转换和处理 +- 分布式数据同步 +- 数据缓存策略 + +### 5. 通知插件 (NotificationPlugin) + +**用途**: 扩展通知渠道 + +**接口方法**: +```python +class NotificationPlugin(BasePlugin): + def send_notification(self, message, recipients): + """发送通知""" + pass + + def format_message(self, data, template): + """格式化消息""" + pass + + def validate_config(self, config): + """验证配置""" + pass +``` + +**应用场景**: +- 新的通知渠道 +- 自定义消息格式 +- 通知规则定制 +- 第三方集成 + +## 🔧 插件管理器 + +### 核心组件 + +#### 1. 插件加载器 (PluginLoader) + +**职责**: 负责插件的发现、加载和初始化 + +```python +class PluginLoader: + def discover_plugins(self, plugin_dir): + """发现插件""" + pass + + def load_plugin(self, plugin_path): + """加载插件""" + pass + + def validate_plugin(self, plugin_class): + """验证插件""" + pass + + def initialize_plugin(self, plugin_instance): + """初始化插件""" + pass +``` + +#### 2. 插件注册表 (PluginRegistry) + +**职责**: 管理已注册的插件信息 + +```python +class PluginRegistry: + def register_plugin(self, plugin_info): + """注册插件""" + pass + + def unregister_plugin(self, plugin_id): + """注销插件""" + pass + + def get_plugin(self, plugin_id): + """获取插件""" + pass + + def list_plugins(self, plugin_type=None): + """列出插件""" + pass +``` + +#### 3. 事件总线 (EventBus) + +**职责**: 处理插件间的事件通信 + +```python +class EventBus: + def subscribe(self, event_type, callback): + """订阅事件""" + pass + + def unsubscribe(self, event_type, callback): + """取消订阅""" + pass + + def publish(self, event_type, event_data): + """发布事件""" + pass + + def broadcast(self, event_data): + """广播事件""" + pass +``` + +#### 4. 生命周期管理器 (LifecycleManager) + +**职责**: 管理插件的生命周期 + +```python +class LifecycleManager: + def start_plugin(self, plugin_id): + """启动插件""" + pass + + def stop_plugin(self, plugin_id): + """停止插件""" + pass + + def restart_plugin(self, plugin_id): + """重启插件""" + pass + + def get_plugin_status(self, plugin_id): + """获取插件状态""" + pass +``` + +## 📋 插件开发规范 + +### 1. 插件结构 + +``` +my_plugin/ +├── __init__.py # 插件入口 +├── plugin.py # 插件主类 +├── config.yaml # 插件配置 +├── requirements.txt # 依赖列表 +├── README.md # 插件说明 +└── tests/ # 测试文件 + └── test_plugin.py +``` + +### 2. 插件元数据 + +```python +# __init__.py +PLUGIN_INFO = { + "name": "my_plugin", + "version": "1.0.0", + "description": "My custom plugin", + "author": "Your Name", + "type": "locust", + "dependencies": ["requests>=2.25.0"], + "config_schema": "config_schema.json" +} +``` + +### 3. 插件基类 + +```python +from abc import ABC, abstractmethod + +class BasePlugin(ABC): + def __init__(self, config=None): + self.config = config or {} + self.enabled = True + self.logger = self._setup_logger() + + @abstractmethod + def initialize(self): + """插件初始化""" + pass + + @abstractmethod + def cleanup(self): + """插件清理""" + pass + + def enable(self): + """启用插件""" + self.enabled = True + + def disable(self): + """禁用插件""" + self.enabled = False + + def _setup_logger(self): + """设置日志""" + pass +``` + +## ⚙️ 插件配置 + +### 全局插件配置 + +```yaml +# conf/plugin_config.yaml +plugins: + enabled: true + auto_load: true + plugin_dirs: + - "src/plugins" + - "custom_plugins" + + # 插件启用列表 + enabled_plugins: + - performance_analyzer + - system_monitor + - notification_service + - data_manager + + # 插件配置 + plugin_configs: + performance_analyzer: + analysis_interval: 10 + metrics_retention: 3600 + + system_monitor: + monitor_interval: 5 + cpu_threshold: 80 + memory_threshold: 85 + + notification_service: + default_channel: "feishu" + retry_count: 3 + timeout: 30 +``` + +### 插件特定配置 + +```yaml +# plugins/my_plugin/config.yaml +plugin: + name: "my_plugin" + enabled: true + +settings: + api_endpoint: "https://api.example.com" + timeout: 30 + retry_count: 3 + +features: + feature_a: true + feature_b: false +``` + +## 🔄 插件生命周期 + +### 1. 加载阶段 +``` +发现插件 → 验证插件 → 加载插件 → 注册插件 +``` + +### 2. 初始化阶段 +``` +读取配置 → 初始化插件 → 注册事件 → 启动服务 +``` + +### 3. 运行阶段 +``` +处理事件 → 执行任务 → 状态监控 → 错误处理 +``` + +### 4. 卸载阶段 +``` +停止服务 → 清理资源 → 注销事件 → 卸载插件 +``` + +## 🛡️ 安全机制 + +### 1. 插件隔离 +- 独立的命名空间 +- 资源访问控制 +- 异常隔离处理 + +### 2. 权限控制 +- 插件权限定义 +- API访问控制 +- 文件系统访问限制 + +### 3. 安全验证 +- 插件签名验证 +- 代码安全扫描 +- 运行时监控 + +## 📊 性能优化 + +### 1. 延迟加载 +- 按需加载插件 +- 懒初始化机制 +- 资源池管理 + +### 2. 缓存机制 +- 插件元数据缓存 +- 配置缓存 +- 结果缓存 + +### 3. 并发处理 +- 异步插件执行 +- 线程池管理 +- 事件队列优化 + +## 📚 相关文档 + +- [插件开发指南](../development/plugin-development.md) - 详细的插件开发教程 +- [模块设计](modules.md) - 各模块详细设计 +- [API参考](../api/plugins.md) - 插件API文档 +- [配置参考](../configuration/plugin-config.md) - 插件配置说明 diff --git a/assets/image-20250531202135855.png b/docs/assets/image-20250531202135855.png similarity index 100% rename from assets/image-20250531202135855.png rename to docs/assets/image-20250531202135855.png diff --git a/assets/image-20250531205049805.png b/docs/assets/image-20250531205049805.png similarity index 100% rename from assets/image-20250531205049805.png rename to docs/assets/image-20250531205049805.png diff --git a/assets/image-20250531205141426.png b/docs/assets/image-20250531205141426.png similarity index 100% rename from assets/image-20250531205141426.png rename to docs/assets/image-20250531205141426.png diff --git a/assets/image-20250531211159524.png b/docs/assets/image-20250531211159524.png similarity index 100% rename from assets/image-20250531211159524.png rename to docs/assets/image-20250531211159524.png diff --git a/assets/image-20250531211606483.png b/docs/assets/image-20250531211606483.png similarity index 100% rename from assets/image-20250531211606483.png rename to docs/assets/image-20250531211606483.png diff --git a/assets/image-20250531212549739.png b/docs/assets/image-20250531212549739.png similarity index 100% rename from assets/image-20250531212549739.png rename to docs/assets/image-20250531212549739.png diff --git a/assets/image-20250601103247786.png b/docs/assets/image-20250601103247786.png similarity index 100% rename from assets/image-20250601103247786.png rename to docs/assets/image-20250601103247786.png diff --git a/assets/image-20250601103425608.png b/docs/assets/image-20250601103425608.png similarity index 100% rename from assets/image-20250601103425608.png rename to docs/assets/image-20250601103425608.png diff --git a/assets/image-20250601103515708.png b/docs/assets/image-20250601103515708.png similarity index 100% rename from assets/image-20250601103515708.png rename to docs/assets/image-20250601103515708.png diff --git a/docs/configuration/development.md b/docs/configuration/development.md new file mode 100644 index 0000000..e791a92 --- /dev/null +++ b/docs/configuration/development.md @@ -0,0 +1,818 @@ +# 开发环境配置 + +本文档详细介绍如何配置Locust性能测试框架的开发环境,包括本地开发、调试配置和开发工具设置。 + +## 🛠️ 开发环境要求 + +### 系统要求 + +```yaml +# development/requirements.yml +system_requirements: + operating_system: + - "macOS 10.15+" + - "Ubuntu 18.04+" + - "Windows 10+" + - "CentOS 7+" + + python: + version: "3.8+" + recommended: "3.9+" + + memory: "4GB+" + disk_space: "2GB+" + + optional_tools: + - docker + - docker-compose + - git + - make +``` + +### 开发工具推荐 + +```yaml +# development/tools.yml +recommended_tools: + ide: + - "PyCharm Professional" + - "Visual Studio Code" + - "Sublime Text" + - "Vim/Neovim" + + version_control: + - "Git" + - "GitHub Desktop" + - "SourceTree" + + api_testing: + - "Postman" + - "Insomnia" + - "curl" + - "HTTPie" + + monitoring: + - "htop" + - "iotop" + - "netstat" + - "tcpdump" +``` + +## 🐍 Python环境配置 + +### 1. 虚拟环境设置 + +```bash +# 使用venv创建虚拟环境 +python -m venv locust-dev +source locust-dev/bin/activate # Linux/Mac +# 或 +locust-dev\Scripts\activate # Windows + +# 使用conda创建环境 +conda create -n locust-dev python=3.9 +conda activate locust-dev + +# 使用pyenv管理Python版本 +pyenv install 3.9.16 +pyenv virtualenv 3.9.16 locust-dev +pyenv activate locust-dev +``` + +### 2. 依赖安装 + +```bash +# 安装基础依赖 +pip install --upgrade pip setuptools wheel + +# 安装开发依赖 +pip install -r requirements-dev.txt + +# 安装项目(可编辑模式) +pip install -e . + +# 验证安装 +locust --version +python -c "import locust; print(locust.__version__)" +``` + +### 3. 开发依赖文件 + +```txt +# requirements-dev.txt +# 基础框架 +locust>=2.14.0 +gevent>=21.12.0 +requests>=2.25.0 + +# 开发工具 +pytest>=7.0.0 +pytest-cov>=4.0.0 +pytest-mock>=3.10.0 +pytest-xdist>=3.2.0 + +# 代码质量 +black>=23.0.0 +isort>=5.12.0 +flake8>=6.0.0 +mypy>=1.0.0 +pre-commit>=3.0.0 + +# 文档工具 +sphinx>=6.0.0 +sphinx-rtd-theme>=1.2.0 +myst-parser>=0.19.0 + +# 性能分析 +memory-profiler>=0.60.0 +line-profiler>=4.0.0 +py-spy>=0.3.14 + +# 数据生成 +faker>=18.0.0 +factory-boy>=3.2.0 + +# 网络工具 +websocket-client>=1.5.0 +paho-mqtt>=1.6.0 + +# 监控集成 +prometheus-client>=0.16.0 +grafana-api>=1.0.3 + +# 通知集成 +slack-sdk>=3.20.0 +requests-oauthlib>=1.3.0 + +# 数据库支持 +sqlalchemy>=2.0.0 +redis>=4.5.0 +pymongo>=4.3.0 + +# 云服务 +boto3>=1.26.0 +google-cloud-storage>=2.7.0 +azure-storage-blob>=12.14.0 +``` + +## 🔧 IDE配置 + +### 1. VS Code配置 + +```json +// .vscode/settings.json +{ + "python.defaultInterpreterPath": "./locust-dev/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": false, + "python.linting.flake8Enabled": true, + "python.linting.mypyEnabled": true, + "python.formatting.provider": "black", + "python.formatting.blackArgs": ["--line-length", "88"], + "python.sortImports.args": ["--profile", "black"], + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true + }, + "files.exclude": { + "**/__pycache__": true, + "**/*.pyc": true, + ".pytest_cache": true, + ".coverage": true, + "htmlcov": true + }, + "python.testing.pytestEnabled": true, + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false +} +``` + +```json +// .vscode/launch.json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Locust Master", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/locust-dev/bin/locust", + "args": [ + "-f", "locustfiles/example.py", + "--master", + "--master-bind-host=0.0.0.0" + ], + "console": "integratedTerminal", + "cwd": "${workspaceFolder}" + }, + { + "name": "Locust Worker", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/locust-dev/bin/locust", + "args": [ + "-f", "locustfiles/example.py", + "--worker", + "--master-host=localhost" + ], + "console": "integratedTerminal", + "cwd": "${workspaceFolder}" + }, + { + "name": "Debug Test", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/locust-dev/bin/python", + "args": [ + "-m", "pytest", + "tests/", + "-v" + ], + "console": "integratedTerminal", + "cwd": "${workspaceFolder}" + } + ] +} +``` + +### 2. PyCharm配置 + +```python +# .idea/runConfigurations/Locust_Master.xml + + + + + +``` + +## 🐳 Docker开发环境 + +### 1. 开发用Docker配置 + +```dockerfile +# Dockerfile.dev +FROM python:3.9-slim + +# 设置工作目录 +WORKDIR /app + +# 安装系统依赖 +RUN apt-get update && apt-get install -y \ + gcc \ + g++ \ + make \ + git \ + curl \ + vim \ + htop \ + && rm -rf /var/lib/apt/lists/* + +# 复制依赖文件 +COPY requirements-dev.txt . + +# 安装Python依赖 +RUN pip install --no-cache-dir -r requirements-dev.txt + +# 复制源码 +COPY . . + +# 安装项目(开发模式) +RUN pip install -e . + +# 设置环境变量 +ENV PYTHONPATH=/app +ENV LOCUST_HOST=http://localhost:8080 + +# 暴露端口 +EXPOSE 8089 5557 + +# 默认命令 +CMD ["bash"] +``` + +### 2. Docker Compose开发配置 + +```yaml +# docker-compose.dev.yml +version: '3.8' + +services: + locust-dev: + build: + context: . + dockerfile: Dockerfile.dev + container_name: locust-dev + ports: + - "8089:8089" + - "5557:5557" + volumes: + - .:/app + - locust-dev-cache:/root/.cache + environment: + - PYTHONPATH=/app + - LOCUST_HOST=http://target-app:8080 + command: tail -f /dev/null # 保持容器运行 + networks: + - dev-network + + target-app: + image: nginx:alpine + container_name: target-app + ports: + - "8080:80" + volumes: + - ./test-data/nginx.conf:/etc/nginx/nginx.conf + networks: + - dev-network + + redis: + image: redis:alpine + container_name: redis-dev + ports: + - "6379:6379" + networks: + - dev-network + + postgres: + image: postgres:13 + container_name: postgres-dev + ports: + - "5432:5432" + environment: + - POSTGRES_DB=locust_dev + - POSTGRES_USER=locust + - POSTGRES_PASSWORD=locust123 + volumes: + - postgres-dev-data:/var/lib/postgresql/data + networks: + - dev-network + +networks: + dev-network: + driver: bridge + +volumes: + locust-dev-cache: + postgres-dev-data: +``` + +### 3. 开发脚本 + +```bash +#!/bin/bash +# scripts/dev-setup.sh + +set -e + +echo "Setting up Locust development environment..." + +# 检查Docker +if ! command -v docker &> /dev/null; then + echo "Docker is required but not installed." + exit 1 +fi + +# 检查Docker Compose +if ! command -v docker-compose &> /dev/null; then + echo "Docker Compose is required but not installed." + exit 1 +fi + +# 构建开发环境 +echo "Building development containers..." +docker-compose -f docker-compose.dev.yml build + +# 启动服务 +echo "Starting development services..." +docker-compose -f docker-compose.dev.yml up -d + +# 等待服务启动 +echo "Waiting for services to start..." +sleep 10 + +# 检查服务状态 +echo "Checking service status..." +docker-compose -f docker-compose.dev.yml ps + +# 运行测试 +echo "Running tests..." +docker-compose -f docker-compose.dev.yml exec locust-dev pytest tests/ -v + +echo "Development environment is ready!" +echo "Access Locust Web UI at: http://localhost:8089" +echo "Target app is available at: http://localhost:8080" + +# 显示有用的命令 +cat << EOF + +Useful commands: + # Enter development container + docker-compose -f docker-compose.dev.yml exec locust-dev bash + + # Run Locust master + docker-compose -f docker-compose.dev.yml exec locust-dev locust -f locustfiles/example.py --master + + # Run tests + docker-compose -f docker-compose.dev.yml exec locust-dev pytest + + # Stop environment + docker-compose -f docker-compose.dev.yml down +EOF +``` + +## 🧪 测试配置 + +### 1. pytest配置 + +```ini +# pytest.ini +[tool:pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = + --strict-markers + --strict-config + --verbose + --tb=short + --cov=src + --cov-report=html + --cov-report=term-missing + --cov-fail-under=80 + --durations=10 + +markers = + unit: Unit tests + integration: Integration tests + performance: Performance tests + slow: Slow running tests + network: Tests requiring network access + +filterwarnings = + ignore::DeprecationWarning + ignore::PendingDeprecationWarning +``` + +### 2. 测试环境变量 + +```bash +# .env.test +# 测试环境配置 +LOCUST_HOST=http://localhost:8080 +LOCUST_USERS=10 +LOCUST_SPAWN_RATE=2 +LOCUST_RUN_TIME=60s + +# 数据库配置 +DATABASE_URL=postgresql://locust:locust123@localhost:5432/locust_test +REDIS_URL=redis://localhost:6379/1 + +# 日志配置 +LOG_LEVEL=DEBUG +LOG_FORMAT=detailed + +# 测试数据 +TEST_DATA_DIR=tests/fixtures +FAKE_DATA_LOCALE=zh_CN + +# API配置 +API_BASE_URL=http://localhost:8080/api +API_TIMEOUT=30 +API_RETRIES=3 +``` + +### 3. 测试辅助工具 + +```python +# tests/conftest.py +import pytest +import tempfile +import shutil +from pathlib import Path +from unittest.mock import Mock, patch + +@pytest.fixture(scope="session") +def temp_dir(): + """创建临时目录""" + temp_path = Path(tempfile.mkdtemp()) + yield temp_path + shutil.rmtree(temp_path) + +@pytest.fixture +def mock_locust_env(): + """模拟Locust环境""" + with patch('locust.env.Environment') as mock_env: + mock_env.stats = Mock() + mock_env.runner = Mock() + yield mock_env + +@pytest.fixture +def sample_config(): + """示例配置""" + return { + "framework": { + "name": "test_framework", + "version": "1.0.0" + }, + "load_shapes": { + "test_shape": { + "type": "linear", + "config": { + "max_users": 100, + "duration": 300 + } + } + } + } + +@pytest.fixture +def mock_http_response(): + """模拟HTTP响应""" + response = Mock() + response.status_code = 200 + response.headers = {"Content-Type": "application/json"} + response.json.return_value = {"status": "success"} + response.text = '{"status": "success"}' + response.content = b'{"status": "success"}' + return response +``` + +## 🔍 调试配置 + +### 1. 日志配置 + +```python +# config/logging_dev.py +import logging +import sys +from pathlib import Path + +def setup_development_logging(): + """设置开发环境日志""" + + # 创建日志目录 + log_dir = Path("logs") + log_dir.mkdir(exist_ok=True) + + # 配置根日志器 + root_logger = logging.getLogger() + root_logger.setLevel(logging.DEBUG) + + # 控制台处理器 + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(logging.INFO) + console_formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + console_handler.setFormatter(console_formatter) + root_logger.addHandler(console_handler) + + # 文件处理器 + file_handler = logging.FileHandler(log_dir / "locust_dev.log") + file_handler.setLevel(logging.DEBUG) + file_formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s' + ) + file_handler.setFormatter(file_formatter) + root_logger.addHandler(file_handler) + + # 设置第三方库日志级别 + logging.getLogger('urllib3').setLevel(logging.WARNING) + logging.getLogger('requests').setLevel(logging.WARNING) + logging.getLogger('gevent').setLevel(logging.WARNING) + + return root_logger + +# 使用示例 +if __name__ == "__main__": + logger = setup_development_logging() + logger.info("Development logging configured") +``` + +### 2. 性能分析配置 + +```python +# tools/profiling.py +import cProfile +import pstats +import io +from functools import wraps +from memory_profiler import profile as memory_profile +from line_profiler import LineProfiler + +def cpu_profile(func): + """CPU性能分析装饰器""" + @wraps(func) + def wrapper(*args, **kwargs): + profiler = cProfile.Profile() + profiler.enable() + + try: + result = func(*args, **kwargs) + finally: + profiler.disable() + + # 输出分析结果 + s = io.StringIO() + ps = pstats.Stats(profiler, stream=s) + ps.sort_stats('cumulative') + ps.print_stats(20) # 显示前20个函数 + + print(f"\n=== CPU Profile for {func.__name__} ===") + print(s.getvalue()) + + return result + return wrapper + +def line_profile(func): + """行级性能分析装饰器""" + @wraps(func) + def wrapper(*args, **kwargs): + profiler = LineProfiler() + profiler.add_function(func) + profiler.enable_by_count() + + try: + result = func(*args, **kwargs) + finally: + profiler.disable_by_count() + + print(f"\n=== Line Profile for {func.__name__} ===") + profiler.print_stats() + + return result + return wrapper + +# 使用示例 +@cpu_profile +@memory_profile +def performance_test_function(): + """性能测试函数""" + import time + time.sleep(1) + data = [i for i in range(100000)] + return sum(data) +``` + +## 🛠️ 开发工具脚本 + +### 1. Makefile + +```makefile +# Makefile +.PHONY: help install test lint format clean dev-setup + +help: + @echo "Available commands:" + @echo " install Install dependencies" + @echo " test Run tests" + @echo " lint Run linting" + @echo " format Format code" + @echo " clean Clean build artifacts" + @echo " dev-setup Setup development environment" + +install: + pip install -r requirements-dev.txt + pip install -e . + +test: + pytest tests/ -v --cov=src + +test-fast: + pytest tests/ -v -x --ff + +lint: + flake8 src/ tests/ + mypy src/ + +format: + black src/ tests/ + isort src/ tests/ + +clean: + find . -type f -name "*.pyc" -delete + find . -type d -name "__pycache__" -delete + rm -rf build/ + rm -rf dist/ + rm -rf *.egg-info/ + rm -rf .pytest_cache/ + rm -rf htmlcov/ + rm -rf .coverage + +dev-setup: + python -m venv locust-dev + ./locust-dev/bin/pip install --upgrade pip + ./locust-dev/bin/pip install -r requirements-dev.txt + ./locust-dev/bin/pip install -e . + ./locust-dev/bin/pre-commit install + +docker-dev: + docker-compose -f docker-compose.dev.yml up -d + +docker-test: + docker-compose -f docker-compose.dev.yml exec locust-dev pytest + +docker-clean: + docker-compose -f docker-compose.dev.yml down -v + docker system prune -f +``` + +### 2. 开发脚本 + +```bash +#!/bin/bash +# scripts/dev-commands.sh + +# 快速启动开发环境 +dev_start() { + echo "Starting development environment..." + source locust-dev/bin/activate + export PYTHONPATH=$(pwd) + export LOCUST_HOST=http://localhost:8080 + echo "Development environment ready!" +} + +# 运行代码质量检查 +quality_check() { + echo "Running code quality checks..." + black --check src/ tests/ + isort --check-only src/ tests/ + flake8 src/ tests/ + mypy src/ + echo "Quality checks completed!" +} + +# 运行完整测试套件 +full_test() { + echo "Running full test suite..." + pytest tests/ -v --cov=src --cov-report=html + echo "Tests completed! Check htmlcov/index.html for coverage report." +} + +# 性能基准测试 +benchmark() { + echo "Running performance benchmarks..." + python -m pytest tests/performance/ -v --benchmark-only + echo "Benchmarks completed!" +} + +# 根据参数执行相应命令 +case "$1" in + "start") + dev_start + ;; + "check") + quality_check + ;; + "test") + full_test + ;; + "benchmark") + benchmark + ;; + *) + echo "Usage: $0 {start|check|test|benchmark}" + exit 1 + ;; +esac +``` + +## 🎉 总结 + +开发环境配置包括: + +1. **Python环境**: 虚拟环境、依赖管理 +2. **IDE配置**: VS Code、PyCharm设置 +3. **Docker环境**: 容器化开发 +4. **测试配置**: pytest、覆盖率 +5. **调试工具**: 日志、性能分析 +6. **开发脚本**: 自动化工具 + +## 📚 相关文档 + +- [贡献指南](../development/contributing.md) - 代码贡献流程 +- [测试指南](../development/testing.md) - 测试编写指南 +- [生产环境配置](production.md) - 生产部署配置 +- [故障排除](../examples/troubleshooting.md) - 开发问题排查 diff --git a/docs/configuration/distributed.md b/docs/configuration/distributed.md new file mode 100644 index 0000000..52557f5 --- /dev/null +++ b/docs/configuration/distributed.md @@ -0,0 +1,761 @@ +# 分布式配置 + +本文档详细介绍如何配置Locust性能测试框架的分布式部署,包括Master-Worker架构、集群管理和负载均衡配置。 + +## 🏗️ 分布式架构 + +### 架构概览 + +```mermaid +graph TD + A[负载均衡器] --> B[Locust Master 1] + A --> C[Locust Master 2] + A --> D[Locust Master 3] + + B --> E[Worker Group 1] + B --> F[Worker Group 2] + C --> G[Worker Group 3] + C --> H[Worker Group 4] + D --> I[Worker Group 5] + D --> J[Worker Group 6] + + E --> K[目标系统集群] + F --> K + G --> K + H --> K + I --> K + J --> K + + L[监控系统] --> A + L --> B + L --> C + L --> D + L --> E + L --> F + L --> G + L --> H + L --> I + L --> J +``` + +### 组件说明 + +```yaml +# distributed/architecture.yml +components: + load_balancer: + description: "负载均衡器,分发Web UI请求" + technology: "Nginx/HAProxy/AWS ALB" + ports: [80, 443] + + master_nodes: + description: "Locust Master节点,协调测试执行" + count: 3 + ports: [8089, 5557] + resources: + cpu: "2 cores" + memory: "4GB" + storage: "20GB" + + worker_groups: + description: "Locust Worker节点组,执行实际测试" + groups: 6 + workers_per_group: 10 + ports: [] + resources: + cpu: "1 core" + memory: "2GB" + storage: "10GB" + + target_system: + description: "被测试的目标系统" + instances: "多实例" + load_balancer: "独立负载均衡" + + monitoring: + description: "监控和日志收集系统" + components: ["Prometheus", "Grafana", "ELK Stack"] +``` + +## 🎛️ Master节点配置 + +### 1. 高可用Master配置 + +```yaml +# config/master-ha.yml +master_cluster: + enabled: true + nodes: + - name: "master-1" + host: "10.0.1.10" + port: 5557 + web_port: 8089 + priority: 100 + + - name: "master-2" + host: "10.0.1.11" + port: 5557 + web_port: 8089 + priority: 90 + + - name: "master-3" + host: "10.0.1.12" + port: 5557 + web_port: 8089 + priority: 80 + + # 选举配置 + election: + enabled: true + timeout: 30 + heartbeat_interval: 5 + + # 数据同步 + synchronization: + enabled: true + sync_interval: 10 + conflict_resolution: "latest_wins" + +# Master节点配置 +master: + # 绑定配置 + bind: + host: "0.0.0.0" + port: 5557 + + # Web UI配置 + web: + host: "0.0.0.0" + port: 8089 + auth_credentials: "admin:secure_password" + + # Worker管理 + workers: + expect_workers: 60 # 期望Worker数量 + expect_workers_max_wait: 120 # 最大等待时间 + heartbeat_liveness: 60 # 心跳存活时间 + heartbeat_interval: 3 # 心跳间隔 + + # 负载分配 + load_distribution: + strategy: "round_robin" # round_robin, least_loaded, weighted + rebalance_interval: 30 + + # 故障处理 + fault_tolerance: + worker_timeout: 60 + retry_failed_workers: true + max_retries: 3 + + # 资源限制 + resources: + max_memory_usage: "4GB" + max_cpu_usage: "80%" + max_connections: 10000 +``` + +### 2. Master启动脚本 + +```bash +#!/bin/bash +# scripts/start-master.sh + +set -e + +# 配置变量 +MASTER_HOST=${MASTER_HOST:-"0.0.0.0"} +MASTER_PORT=${MASTER_PORT:-5557} +WEB_HOST=${WEB_HOST:-"0.0.0.0"} +WEB_PORT=${WEB_PORT:-8089} +LOCUSTFILE=${LOCUSTFILE:-"locustfiles/distributed.py"} +EXPECT_WORKERS=${EXPECT_WORKERS:-60} + +# 日志配置 +LOG_LEVEL=${LOG_LEVEL:-"INFO"} +LOG_FILE=${LOG_FILE:-"/var/log/locust/master.log"} + +# 创建日志目录 +mkdir -p $(dirname $LOG_FILE) + +# 健康检查函数 +health_check() { + local max_attempts=30 + local attempt=1 + + while [ $attempt -le $max_attempts ]; do + if curl -f http://${WEB_HOST}:${WEB_PORT}/stats/requests > /dev/null 2>&1; then + echo "Master is healthy" + return 0 + fi + + echo "Health check attempt $attempt/$max_attempts failed" + sleep 2 + ((attempt++)) + done + + echo "Master health check failed" + return 1 +} + +# 启动Master +echo "Starting Locust Master..." +echo "Configuration:" +echo " Host: ${MASTER_HOST}:${MASTER_PORT}" +echo " Web UI: ${WEB_HOST}:${WEB_PORT}" +echo " Locustfile: ${LOCUSTFILE}" +echo " Expected Workers: ${EXPECT_WORKERS}" + +# 启动命令 +locust \ + --master \ + --master-bind-host=${MASTER_HOST} \ + --master-bind-port=${MASTER_PORT} \ + --web-host=${WEB_HOST} \ + --web-port=${WEB_PORT} \ + --expect-workers=${EXPECT_WORKERS} \ + --loglevel=${LOG_LEVEL} \ + --logfile=${LOG_FILE} \ + -f ${LOCUSTFILE} & + +MASTER_PID=$! +echo "Master started with PID: $MASTER_PID" + +# 等待启动 +sleep 10 + +# 健康检查 +if health_check; then + echo "Master is running successfully" + echo $MASTER_PID > /var/run/locust-master.pid +else + echo "Master failed to start properly" + kill $MASTER_PID 2>/dev/null || true + exit 1 +fi + +# 保持前台运行 +wait $MASTER_PID +``` + +## 👥 Worker节点配置 + +### 1. Worker配置 + +```yaml +# config/worker.yml +worker: + # Master连接配置 + master: + host: "master.locust.local" # Master主机名或IP + port: 5557 + connection_timeout: 30 + reconnect_interval: 5 + max_reconnect_attempts: 10 + + # Worker标识 + identity: + node_id: "${HOSTNAME}" + tags: ["region:us-west", "zone:a", "type:standard"] + + # 资源配置 + resources: + max_users: 1000 # 单个Worker最大用户数 + cpu_limit: "1000m" # CPU限制 + memory_limit: "2Gi" # 内存限制 + + # 性能调优 + performance: + gevent_pool_size: 1000 + connection_pool_size: 100 + dns_cache_size: 1000 + + # 监控配置 + monitoring: + metrics_enabled: true + metrics_port: 9090 + health_check_port: 8080 + + # 日志配置 + logging: + level: "INFO" + format: "json" + file: "/var/log/locust/worker.log" + max_size: "100MB" + backup_count: 5 +``` + +### 2. Worker自动扩缩容 + +```yaml +# k8s/worker-hpa.yml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: locust-worker-hpa + namespace: locust +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: locust-worker + minReplicas: 10 + maxReplicas: 100 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 + - type: Pods + pods: + metric: + name: locust_users_per_worker + target: + type: AverageValue + averageValue: "500" + behavior: + scaleUp: + stabilizationWindowSeconds: 60 + policies: + - type: Percent + value: 50 + periodSeconds: 60 + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodSeconds: 60 + +--- +# Worker部署配置 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: locust-worker + namespace: locust +spec: + replicas: 10 + selector: + matchLabels: + app: locust-worker + template: + metadata: + labels: + app: locust-worker + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9090" + prometheus.io/path: "/metrics" + spec: + containers: + - name: locust-worker + image: locust-framework:latest + ports: + - containerPort: 9090 + name: metrics + - containerPort: 8080 + name: health + env: + - name: LOCUST_MODE + value: "worker" + - name: LOCUST_MASTER_HOST + value: "locust-master-service" + - name: LOCUST_MASTER_PORT + value: "5557" + - name: WORKER_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + resources: + requests: + cpu: "500m" + memory: "1Gi" + limits: + cpu: "1000m" + memory: "2Gi" + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - name: locustfiles + mountPath: /app/locustfiles + - name: config + mountPath: /app/config + volumes: + - name: locustfiles + configMap: + name: locustfiles-config + - name: config + configMap: + name: worker-config +``` + +### 3. Worker启动脚本 + +```bash +#!/bin/bash +# scripts/start-worker.sh + +set -e + +# 配置变量 +MASTER_HOST=${MASTER_HOST:-"master.locust.local"} +MASTER_PORT=${MASTER_PORT:-5557} +LOCUSTFILE=${LOCUSTFILE:-"locustfiles/distributed.py"} +WORKER_NODE_ID=${WORKER_NODE_ID:-$(hostname)} + +# 日志配置 +LOG_LEVEL=${LOG_LEVEL:-"INFO"} +LOG_FILE=${LOG_FILE:-"/var/log/locust/worker-${WORKER_NODE_ID}.log"} + +# 创建日志目录 +mkdir -p $(dirname $LOG_FILE) + +# Master连接检查 +check_master_connection() { + local max_attempts=30 + local attempt=1 + + while [ $attempt -le $max_attempts ]; do + if nc -z ${MASTER_HOST} ${MASTER_PORT}; then + echo "Master connection successful" + return 0 + fi + + echo "Master connection attempt $attempt/$max_attempts failed" + sleep 2 + ((attempt++)) + done + + echo "Failed to connect to Master" + return 1 +} + +# 启动Worker +echo "Starting Locust Worker..." +echo "Configuration:" +echo " Worker ID: ${WORKER_NODE_ID}" +echo " Master: ${MASTER_HOST}:${MASTER_PORT}" +echo " Locustfile: ${LOCUSTFILE}" + +# 检查Master连接 +if ! check_master_connection; then + echo "Cannot connect to Master, exiting" + exit 1 +fi + +# 启动命令 +exec locust \ + --worker \ + --master-host=${MASTER_HOST} \ + --master-port=${MASTER_PORT} \ + --loglevel=${LOG_LEVEL} \ + --logfile=${LOG_FILE} \ + -f ${LOCUSTFILE} +``` + +## 🔄 负载均衡配置 + +### 1. Nginx负载均衡 + +```nginx +# nginx/locust-lb.conf +upstream locust_masters { + least_conn; + server 10.0.1.10:8089 weight=3 max_fails=3 fail_timeout=30s; + server 10.0.1.11:8089 weight=2 max_fails=3 fail_timeout=30s; + server 10.0.1.12:8089 weight=1 max_fails=3 fail_timeout=30s; +} + +server { + listen 80; + listen 443 ssl http2; + server_name locust.company.com; + + # SSL配置 + ssl_certificate /etc/ssl/certs/locust.crt; + ssl_certificate_key /etc/ssl/private/locust.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512; + + # 安全头 + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + + # 代理配置 + location / { + proxy_pass http://locust_masters; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket支持 + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 超时配置 + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + + # 缓冲配置 + proxy_buffering off; + proxy_request_buffering off; + } + + # 健康检查端点 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # 静态资源缓存 + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} +``` + +### 2. HAProxy配置 + +```haproxy +# haproxy/haproxy.cfg +global + daemon + maxconn 4096 + log stdout local0 + +defaults + mode http + timeout connect 5000ms + timeout client 50000ms + timeout server 50000ms + option httplog + option dontlognull + +# 统计页面 +stats enable +stats uri /stats +stats refresh 30s +stats admin if TRUE + +# Locust Master负载均衡 +frontend locust_frontend + bind *:80 + bind *:443 ssl crt /etc/ssl/certs/locust.pem + redirect scheme https if !{ ssl_fc } + + # ACL规则 + acl is_websocket hdr(Upgrade) -i websocket + acl is_api path_beg /api + acl is_stats path_beg /stats + + # 路由规则 + use_backend locust_masters if !is_stats + use_backend stats_backend if is_stats + +backend locust_masters + balance leastconn + option httpchk GET /stats/requests + + server master1 10.0.1.10:8089 check weight 3 + server master2 10.0.1.11:8089 check weight 2 + server master3 10.0.1.12:8089 check weight 1 + +backend stats_backend + stats enable + stats uri /stats + stats refresh 30s +``` + +## 📊 监控和日志 + +### 1. Prometheus监控配置 + +```yaml +# monitoring/prometheus.yml +global: + scrape_interval: 15s + evaluation_interval: 15s + +rule_files: + - "locust_rules.yml" + +scrape_configs: + # Locust Master监控 + - job_name: 'locust-master' + static_configs: + - targets: + - 'master-1:9090' + - 'master-2:9090' + - 'master-3:9090' + scrape_interval: 10s + metrics_path: '/metrics' + + # Locust Worker监控 + - job_name: 'locust-worker' + kubernetes_sd_configs: + - role: pod + namespaces: + names: + - locust + relabel_configs: + - source_labels: [__meta_kubernetes_pod_label_app] + action: keep + regex: locust-worker + - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] + action: keep + regex: true + - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port] + action: replace + target_label: __address__ + regex: (.+) + replacement: ${1}:9090 + + # 系统监控 + - job_name: 'node-exporter' + static_configs: + - targets: + - 'node1:9100' + - 'node2:9100' + - 'node3:9100' + +alerting: + alertmanagers: + - static_configs: + - targets: + - 'alertmanager:9093' +``` + +### 2. 告警规则 + +```yaml +# monitoring/locust_rules.yml +groups: + - name: locust_alerts + rules: + # Master节点告警 + - alert: LocustMasterDown + expr: up{job="locust-master"} == 0 + for: 1m + labels: + severity: critical + annotations: + summary: "Locust Master节点宕机" + description: "Master节点 {{ $labels.instance }} 已宕机超过1分钟" + + # Worker连接告警 + - alert: LocustWorkerDisconnected + expr: locust_workers_connected < 50 + for: 2m + labels: + severity: warning + annotations: + summary: "Locust Worker连接数过低" + description: "当前连接的Worker数量为 {{ $value }},低于预期" + + # 性能告警 + - alert: LocustHighErrorRate + expr: locust_errors_rate > 0.05 + for: 5m + labels: + severity: warning + annotations: + summary: "Locust错误率过高" + description: "错误率为 {{ $value | humanizePercentage }},超过5%" + + # 资源告警 + - alert: LocustHighMemoryUsage + expr: process_resident_memory_bytes{job=~"locust-.*"} / 1024 / 1024 / 1024 > 1.5 + for: 5m + labels: + severity: warning + annotations: + summary: "Locust内存使用过高" + description: "{{ $labels.instance }} 内存使用量为 {{ $value | humanize }}GB" +``` + +## 🚀 部署脚本 + +### 1. 完整部署脚本 + +```bash +#!/bin/bash +# scripts/deploy-distributed.sh + +set -e + +# 配置变量 +NAMESPACE=${NAMESPACE:-"locust"} +MASTER_REPLICAS=${MASTER_REPLICAS:-3} +WORKER_REPLICAS=${WORKER_REPLICAS:-60} +IMAGE_TAG=${IMAGE_TAG:-"latest"} + +echo "Deploying Locust distributed cluster..." +echo "Namespace: $NAMESPACE" +echo "Master replicas: $MASTER_REPLICAS" +echo "Worker replicas: $WORKER_REPLICAS" +echo "Image tag: $IMAGE_TAG" + +# 创建命名空间 +kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f - + +# 部署配置 +kubectl apply -f k8s/configmaps/ -n $NAMESPACE +kubectl apply -f k8s/secrets/ -n $NAMESPACE + +# 部署Master +envsubst < k8s/master-deployment.yml | kubectl apply -f - -n $NAMESPACE +kubectl apply -f k8s/master-service.yml -n $NAMESPACE + +# 等待Master启动 +echo "Waiting for Master to be ready..." +kubectl wait --for=condition=available --timeout=300s deployment/locust-master -n $NAMESPACE + +# 部署Worker +envsubst < k8s/worker-deployment.yml | kubectl apply -f - -n $NAMESPACE + +# 部署HPA +kubectl apply -f k8s/worker-hpa.yml -n $NAMESPACE + +# 部署监控 +kubectl apply -f monitoring/ -n $NAMESPACE + +# 部署Ingress +kubectl apply -f k8s/ingress.yml -n $NAMESPACE + +echo "Deployment completed!" +echo "Access Locust Web UI at: https://locust.company.com" + +# 显示状态 +kubectl get all -n $NAMESPACE +``` + +## 📚 相关文档 + +- [生产环境配置](production.md) - 生产部署指南 +- [监控配置](monitoring-config.md) - 监控系统配置 +- [故障排除](../examples/troubleshooting.md) - 分布式问题排查 +- [性能优化](../examples/best-practices.md) - 性能优化建议 diff --git a/docs/configuration/framework-config.md b/docs/configuration/framework-config.md new file mode 100644 index 0000000..b884069 --- /dev/null +++ b/docs/configuration/framework-config.md @@ -0,0 +1,574 @@ +# 框架配置参考 + +本文档详细介绍Locust性能测试框架的配置选项,包括核心配置、插件配置、环境配置等。 + +## 📋 配置概述 + +框架使用Dynaconf进行配置管理,支持多种配置源和环境隔离: + +```python +# 配置优先级(从高到低) +1. 环境变量 +2. 命令行参数 +3. 用户配置文件 +4. 环境特定配置 +5. 默认配置 +``` + +## 🔧 核心配置 + +### 主配置文件 (conf/settings.toml) + +```toml +# 基础配置 +[default] +# 框架基础设置 +framework_name = "Locust Performance Testing Framework" +version = "2.0.0" +debug = false +log_level = "INFO" + +# 测试配置 +[default.test] +# 默认测试设置 +default_host = "http://localhost:8080" +default_users = 10 +default_spawn_rate = 1 +default_run_time = "10m" +stop_timeout = 30 + +# Web UI配置 +[default.web] +enabled = true +host = "0.0.0.0" +port = 8089 +auth_enabled = false +username = "admin" +password = "admin" + +# 分布式配置 +[default.distributed] +master_host = "127.0.0.1" +master_port = 5557 +worker_timeout = 60 +expect_workers = 1 +heartbeat_interval = 3 + +# 日志配置 +[default.logging] +level = "INFO" +format = "{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{function}:{line} | {message}" +rotation = "100 MB" +retention = "30 days" +compression = "gz" + +# 输出配置 +[default.output] +reports_dir = "reports" +data_dir = "data" +logs_dir = "logs" +auto_cleanup = true +cleanup_days = 7 + +# 性能配置 +[default.performance] +max_workers = 100 +connection_pool_size = 50 +request_timeout = 30 +max_retries = 3 +backoff_factor = 1.0 +``` + +### 环境特定配置 + +#### 开发环境 (conf/environments/development.toml) + +```toml +[default] +debug = true +log_level = "DEBUG" + +[default.test] +default_host = "http://localhost:3000" +default_users = 5 +default_run_time = "5m" + +[default.web] +port = 8089 + +[default.logging] +level = "DEBUG" + +[default.performance] +max_workers = 10 +connection_pool_size = 10 +``` + +#### 测试环境 (conf/environments/testing.toml) + +```toml +[default] +debug = false +log_level = "INFO" + +[default.test] +default_host = "http://test-api.example.com" +default_users = 50 +default_run_time = "30m" + +[default.distributed] +expect_workers = 3 + +[default.performance] +max_workers = 200 +connection_pool_size = 100 +``` + +#### 生产环境 (conf/environments/production.toml) + +```toml +[default] +debug = false +log_level = "WARNING" + +[default.test] +default_host = "http://api.example.com" +default_users = 1000 +default_run_time = "2h" + +[default.web] +auth_enabled = true +username = "${WEB_USERNAME}" +password = "${WEB_PASSWORD}" + +[default.distributed] +expect_workers = 10 + +[default.performance] +max_workers = 1000 +connection_pool_size = 500 +request_timeout = 60 + +[default.security] +ssl_verify = true +api_key = "${API_KEY}" +``` + +## 🔌 插件配置 + +### 插件管理配置 + +```toml +# conf/plugins/plugin_config.toml +[plugins] +# 插件发现路径 +discovery_paths = [ + "src/plugins/builtin", + "plugins/custom", + "~/.locust/plugins" +] + +# 自动加载插件 +auto_load = true +load_timeout = 30 + +# 插件依赖检查 +check_dependencies = true +install_missing = false + +# 插件配置 +[plugins.performance_analyzer] +enabled = true +config = { output_formats = ["html", "json"], include_charts = true } + +[plugins.system_monitor] +enabled = true +config = { + interval = 5, + metrics = ["cpu", "memory", "disk", "network"], + alert_thresholds = { cpu = 80, memory = 85 } +} + +[plugins.notification_service] +enabled = true +config = { + channels = ["feishu", "email"], + feishu_webhook = "${FEISHU_WEBHOOK}", + email_smtp = "smtp.example.com" +} +``` + +### 分析插件配置 + +```toml +[analysis] +# 性能分析配置 +[analysis.performance_analyzer] +enabled = true + +# 响应时间阈值 (毫秒) +[analysis.performance_analyzer.response_time_thresholds] +excellent = 500 +good = 1000 +acceptable = 2000 +poor = 5000 + +# 吞吐量阈值 (TPS) +[analysis.performance_analyzer.throughput_thresholds] +excellent = 1000 +good = 500 +acceptable = 100 +poor = 50 + +# 错误率阈值 (百分比) +[analysis.performance_analyzer.error_rate_thresholds] +excellent = 0.1 +good = 1.0 +acceptable = 5.0 +poor = 10.0 + +# 指标权重 +[analysis.performance_analyzer.weights] +response_time = 0.4 +throughput = 0.3 +error_rate = 0.2 +stability = 0.1 + +# 趋势分析配置 +[analysis.trend_analyzer] +enabled = true +history_days = 30 +prediction_days = 7 +confidence_level = 0.95 +``` + +### 监控插件配置 + +```toml +[monitoring] +# 性能监控配置 +[monitoring.performance_monitor] +enabled = true +interval = 5 +buffer_size = 1000 + +# 告警管理配置 +[monitoring.alert_manager] +enabled = true +check_interval = 10 +max_alerts_per_minute = 10 + +# 告警规则 +[[monitoring.alert_rules]] +name = "high_response_time" +metric = "avg_response_time" +condition = "> 2000" +duration = "5m" +severity = "warning" +message = "平均响应时间超过2秒" + +[[monitoring.alert_rules]] +name = "high_error_rate" +metric = "error_rate" +condition = "> 5" +duration = "2m" +severity = "critical" +message = "错误率超过5%" + +[[monitoring.alert_rules]] +name = "low_throughput" +metric = "requests_per_second" +condition = "< 10" +duration = "3m" +severity = "warning" +message = "吞吐量低于10 RPS" +``` + +### 通知插件配置 + +```toml +[notifications] +# 飞书通知配置 +[notifications.feishu] +enabled = true +webhook_url = "${FEISHU_WEBHOOK_URL}" +secret = "${FEISHU_SECRET}" +at_all = false +at_users = [] + +# 钉钉通知配置 +[notifications.dingtalk] +enabled = false +webhook_url = "${DINGTALK_WEBHOOK_URL}" +secret = "${DINGTALK_SECRET}" + +# 邮件通知配置 +[notifications.email] +enabled = true +smtp_server = "smtp.example.com" +smtp_port = 587 +username = "${EMAIL_USERNAME}" +password = "${EMAIL_PASSWORD}" +from_addr = "noreply@example.com" +to_addrs = ["admin@example.com"] +use_tls = true + +# 企业微信通知配置 +[notifications.wechat_work] +enabled = false +webhook_url = "${WECHAT_WEBHOOK_URL}" +``` + +## 📊 数据管理配置 + +```toml +[data_manager] +# 数据生成配置 +[data_manager.generator] +enabled = true +locale = "zh_CN" +seed = 12345 +cache_size = 10000 + +# 数据提供配置 +[data_manager.provider] +enabled = true +default_strategy = "round_robin" +data_sources = [ + { type = "file", path = "test_data/users.csv" }, + { type = "database", connection = "mysql://user:pass@localhost/testdb" }, + { type = "api", url = "http://api.example.com/test-data" } +] + +# 数据分发配置 +[data_manager.distributor] +enabled = true +sync_interval = 60 +batch_size = 1000 +compression = true +``` + +## 🔒 安全配置 + +```toml +[security] +# 认证配置 +[security.authentication] +enabled = true +method = "token" # token, basic, oauth +token_header = "Authorization" +token_prefix = "Bearer" + +# SSL/TLS配置 +[security.ssl] +verify = true +cert_file = "" +key_file = "" +ca_file = "" + +# API安全配置 +[security.api] +rate_limit = 1000 +rate_limit_window = 3600 +api_key_header = "X-API-Key" +allowed_origins = ["*"] +``` + +## 🚀 性能优化配置 + +```toml +[performance] +# 连接池配置 +[performance.connection_pool] +max_connections = 100 +max_connections_per_host = 20 +keepalive_timeout = 30 +connection_timeout = 10 + +# 请求配置 +[performance.requests] +timeout = 30 +max_retries = 3 +backoff_factor = 1.0 +retry_on_status = [500, 502, 503, 504] + +# 内存配置 +[performance.memory] +max_memory_usage = "2GB" +gc_threshold = 0.8 +cache_size = 10000 + +# 并发配置 +[performance.concurrency] +max_workers = 1000 +worker_queue_size = 10000 +task_timeout = 300 +``` + +## 📝 日志配置 + +### 详细日志配置 (conf/logging.conf) + +```ini +[loggers] +keys=root,locust,framework + +[handlers] +keys=consoleHandler,fileHandler,rotatingFileHandler + +[formatters] +keys=simpleFormatter,detailedFormatter + +[logger_root] +level=INFO +handlers=consoleHandler,rotatingFileHandler + +[logger_locust] +level=INFO +handlers=fileHandler +qualname=locust +propagate=0 + +[logger_framework] +level=DEBUG +handlers=rotatingFileHandler +qualname=framework +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=simpleFormatter +args=(sys.stdout,) + +[handler_fileHandler] +class=FileHandler +level=INFO +formatter=detailedFormatter +args=('logs/locust.log',) + +[handler_rotatingFileHandler] +class=handlers.RotatingFileHandler +level=DEBUG +formatter=detailedFormatter +args=('logs/framework.log', 'a', 10485760, 5) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s + +[formatter_detailedFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s +``` + +## 🔧 配置使用示例 + +### 1. 基础配置加载 + +```python +from dynaconf import Dynaconf + +# 加载配置 +settings = Dynaconf( + envvar_prefix="LOCUST", + settings_files=['conf/settings.toml', 'conf/.secrets.toml'], + environments=True, + load_dotenv=True, + env_switcher="LOCUST_ENV" +) + +# 使用配置 +print(f"当前环境: {settings.current_env}") +print(f"默认主机: {settings.test.default_host}") +print(f"Web端口: {settings.web.port}") +``` + +### 2. 环境切换 + +```bash +# 设置环境变量 +export LOCUST_ENV=production + +# 或在代码中切换 +settings.setenv('production') +``` + +### 3. 动态配置更新 + +```python +# 运行时更新配置 +settings.set('test.default_users', 100) +settings.set('web.port', 9089) + +# 保存配置 +settings.save() +``` + +### 4. 配置验证 + +```python +from pydantic import BaseModel, validator + +class TestConfig(BaseModel): + default_host: str + default_users: int + default_spawn_rate: float + + @validator('default_users') + def validate_users(cls, v): + if v <= 0: + raise ValueError('用户数必须大于0') + return v + + @validator('default_spawn_rate') + def validate_spawn_rate(cls, v): + if v <= 0: + raise ValueError('生成速率必须大于0') + return v + +# 验证配置 +try: + test_config = TestConfig(**settings.test) + print("配置验证通过") +except Exception as e: + print(f"配置验证失败: {e}") +``` + +## 🔐 敏感信息管理 + +### 1. 环境变量 + +```bash +# .env 文件 +LOCUST_ENV=production +FEISHU_WEBHOOK_URL=https://open.feishu.cn/open-apis/bot/v2/hook/xxx +EMAIL_PASSWORD=your_email_password +API_KEY=your_api_key +``` + +### 2. 密钥文件 + +```toml +# conf/.secrets.toml (不提交到版本控制) +[default] +api_key = "your_secret_api_key" +database_password = "your_db_password" + +[production] +ssl_cert = "/path/to/cert.pem" +ssl_key = "/path/to/key.pem" +``` + +## ⚠️ 配置注意事项 + +1. **安全性**: 敏感信息使用环境变量或密钥文件 +2. **验证**: 启动时验证关键配置项 +3. **备份**: 定期备份配置文件 +4. **版本控制**: 不要提交包含敏感信息的配置文件 +5. **文档**: 及时更新配置文档 + +## 🔗 相关文档 + +- [插件配置](plugin-config.md) - 插件专用配置 +- [环境配置](environment-config.md) - 环境特定配置 +- [安全配置](security-config.md) - 安全相关配置 +- [性能调优](../development/performance-tuning.md) - 性能优化指南 diff --git a/docs/configuration/monitoring-config.md b/docs/configuration/monitoring-config.md new file mode 100644 index 0000000..ed5bf44 --- /dev/null +++ b/docs/configuration/monitoring-config.md @@ -0,0 +1,917 @@ +# 监控配置 + +本文档详细介绍如何配置Locust性能测试框架的监控系统,包括指标收集、告警设置和可视化配置。 + +## 🎯 监控架构 + +### 监控组件 + +```yaml +# monitoring/architecture.yml +monitoring_stack: + metrics_collection: + - prometheus # 指标收集 + - node_exporter # 系统指标 + - locust_exporter # Locust指标 + + visualization: + - grafana # 仪表板 + - kibana # 日志分析 + + alerting: + - alertmanager # 告警管理 + - webhook_alerts # 自定义告警 + + storage: + - prometheus_tsdb # 时序数据库 + - elasticsearch # 日志存储 +``` + +### 数据流 + +```mermaid +graph TD + A[Locust Framework] --> B[Metrics Exporter] + B --> C[Prometheus] + C --> D[Grafana] + C --> E[AlertManager] + E --> F[Notification Channels] + A --> G[Log Aggregator] + G --> H[Elasticsearch] + H --> I[Kibana] +``` + +## 📊 Prometheus配置 + +### 1. 基础配置 + +```yaml +# prometheus/prometheus.yml +global: + scrape_interval: 15s + evaluation_interval: 15s + external_labels: + cluster: 'locust-cluster' + environment: 'production' + +rule_files: + - "rules/*.yml" + +scrape_configs: + # Locust Master节点 + - job_name: 'locust-master' + static_configs: + - targets: ['locust-master:8089'] + metrics_path: '/stats/requests' + params: + format: ['prometheus'] + scrape_interval: 30s + scrape_timeout: 10s + + # Locust Worker节点 + - job_name: 'locust-workers' + static_configs: + - targets: + - 'locust-worker-1:8089' + - 'locust-worker-2:8089' + - 'locust-worker-3:8089' + metrics_path: '/stats/requests' + params: + format: ['prometheus'] + scrape_interval: 30s + + # 系统指标 + - job_name: 'node-exporter' + static_configs: + - targets: + - 'locust-master:9100' + - 'locust-worker-1:9100' + - 'locust-worker-2:9100' + - 'locust-worker-3:9100' + scrape_interval: 15s + + # 应用指标 + - job_name: 'locust-app-metrics' + static_configs: + - targets: ['locust-master:9090'] + metrics_path: '/metrics' + scrape_interval: 15s + +alerting: + alertmanagers: + - static_configs: + - targets: + - alertmanager:9093 + timeout: 10s + api_version: v2 +``` + +### 2. 自定义指标导出器 + +```python +# monitoring/locust_exporter.py +from prometheus_client import start_http_server, Gauge, Counter, Histogram +from locust import events +import time +import threading + +class LocustMetricsExporter: + """Locust指标导出器""" + + def __init__(self, port=9090): + self.port = port + + # 定义指标 + self.request_total = Counter( + 'locust_requests_total', + 'Total number of requests', + ['method', 'name', 'status'] + ) + + self.request_duration = Histogram( + 'locust_request_duration_seconds', + 'Request duration in seconds', + ['method', 'name'], + buckets=[0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0] + ) + + self.current_users = Gauge( + 'locust_current_users', + 'Current number of users' + ) + + self.rps = Gauge( + 'locust_requests_per_second', + 'Current requests per second' + ) + + self.error_rate = Gauge( + 'locust_error_rate_percent', + 'Current error rate percentage' + ) + + self.response_time_percentiles = Gauge( + 'locust_response_time_percentile_seconds', + 'Response time percentiles', + ['percentile'] + ) + + # 业务指标 + self.business_metrics = {} + + # 注册事件监听器 + self.register_listeners() + + def register_listeners(self): + """注册Locust事件监听器""" + + @events.request.add_listener + def on_request(request_type, name, response_time, response_length, exception, context, **kwargs): + # 记录请求总数 + status = "success" if not exception else "failure" + self.request_total.labels( + method=request_type, + name=name, + status=status + ).inc() + + # 记录请求耗时 + if not exception: + self.request_duration.labels( + method=request_type, + name=name + ).observe(response_time / 1000.0) # 转换为秒 + + @events.user_count_changed.add_listener + def on_user_count_changed(user_count, **kwargs): + self.current_users.set(user_count) + + # 定期更新统计指标 + def update_stats(): + while True: + try: + from locust import stats + total_stats = stats.total + + if total_stats.num_requests > 0: + # 更新RPS + self.rps.set(total_stats.current_rps) + + # 更新错误率 + error_rate = (total_stats.num_failures / total_stats.num_requests) * 100 + self.error_rate.set(error_rate) + + # 更新响应时间百分位数 + self.response_time_percentiles.labels(percentile="50").set( + total_stats.get_response_time_percentile(0.5) / 1000.0 + ) + self.response_time_percentiles.labels(percentile="95").set( + total_stats.get_response_time_percentile(0.95) / 1000.0 + ) + self.response_time_percentiles.labels(percentile="99").set( + total_stats.get_response_time_percentile(0.99) / 1000.0 + ) + + except Exception as e: + print(f"Error updating stats: {e}") + + time.sleep(5) # 每5秒更新一次 + + # 启动统计更新线程 + stats_thread = threading.Thread(target=update_stats, daemon=True) + stats_thread.start() + + def add_business_metric(self, name: str, description: str, metric_type: str = 'gauge', labels: list = None): + """添加业务指标""" + labels = labels or [] + + if metric_type == 'gauge': + metric = Gauge(name, description, labels) + elif metric_type == 'counter': + metric = Counter(name, description, labels) + elif metric_type == 'histogram': + metric = Histogram(name, description, labels) + else: + raise ValueError(f"Unsupported metric type: {metric_type}") + + self.business_metrics[name] = metric + return metric + + def start_server(self): + """启动指标服务器""" + start_http_server(self.port) + print(f"Metrics server started on port {self.port}") + +# 使用指标导出器 +exporter = LocustMetricsExporter() + +@events.init.add_listener +def on_locust_init(environment, **kwargs): + exporter.start_server() + + # 添加自定义业务指标 + login_success_metric = exporter.add_business_metric( + 'locust_login_success_total', + 'Total successful logins', + 'counter', + ['user_type'] + ) + + order_processing_time = exporter.add_business_metric( + 'locust_order_processing_seconds', + 'Order processing time', + 'histogram', + ['order_type'] + ) +``` + +## 📈 Grafana配置 + +### 1. 数据源配置 + +```yaml +# grafana/datasources.yml +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: true + + - name: Elasticsearch + type: elasticsearch + access: proxy + url: http://elasticsearch:9200 + database: "locust-logs-*" + interval: Daily + timeField: "@timestamp" +``` + +### 2. 仪表板配置 + +```json +{ + "dashboard": { + "id": null, + "title": "Locust Performance Dashboard", + "tags": ["locust", "performance"], + "timezone": "browser", + "panels": [ + { + "id": 1, + "title": "Request Rate", + "type": "graph", + "targets": [ + { + "expr": "rate(locust_requests_total[5m])", + "legendFormat": "{{method}} {{name}}", + "refId": "A" + } + ], + "yAxes": [ + { + "label": "Requests/sec", + "min": 0 + } + ], + "gridPos": {"h": 8, "w": 12, "x": 0, "y": 0} + }, + { + "id": 2, + "title": "Response Time", + "type": "graph", + "targets": [ + { + "expr": "locust_response_time_percentile_seconds{percentile=\"50\"}", + "legendFormat": "50th percentile", + "refId": "A" + }, + { + "expr": "locust_response_time_percentile_seconds{percentile=\"95\"}", + "legendFormat": "95th percentile", + "refId": "B" + }, + { + "expr": "locust_response_time_percentile_seconds{percentile=\"99\"}", + "legendFormat": "99th percentile", + "refId": "C" + } + ], + "yAxes": [ + { + "label": "Seconds", + "min": 0 + } + ], + "gridPos": {"h": 8, "w": 12, "x": 12, "y": 0} + }, + { + "id": 3, + "title": "Error Rate", + "type": "singlestat", + "targets": [ + { + "expr": "locust_error_rate_percent", + "refId": "A" + } + ], + "valueName": "current", + "format": "percent", + "thresholds": "5,10", + "colorBackground": true, + "gridPos": {"h": 4, "w": 6, "x": 0, "y": 8} + }, + { + "id": 4, + "title": "Current Users", + "type": "singlestat", + "targets": [ + { + "expr": "locust_current_users", + "refId": "A" + } + ], + "valueName": "current", + "format": "short", + "gridPos": {"h": 4, "w": 6, "x": 6, "y": 8} + }, + { + "id": 5, + "title": "System Resources", + "type": "graph", + "targets": [ + { + "expr": "100 - (avg by (instance) (rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)", + "legendFormat": "CPU Usage - {{instance}}", + "refId": "A" + }, + { + "expr": "(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100", + "legendFormat": "Memory Usage - {{instance}}", + "refId": "B" + } + ], + "yAxes": [ + { + "label": "Percent", + "min": 0, + "max": 100 + } + ], + "gridPos": {"h": 8, "w": 12, "x": 12, "y": 8} + } + ], + "time": { + "from": "now-1h", + "to": "now" + }, + "refresh": "5s" + } +} +``` + +### 3. 告警规则 + +```yaml +# grafana/alerts.yml +groups: + - name: locust_alerts + rules: + - alert: HighErrorRate + expr: locust_error_rate_percent > 5 + for: 2m + labels: + severity: warning + service: locust + annotations: + summary: "High error rate detected" + description: "Error rate is {{ $value }}% for more than 2 minutes" + + - alert: HighResponseTime + expr: locust_response_time_percentile_seconds{percentile="95"} > 2 + for: 5m + labels: + severity: warning + service: locust + annotations: + summary: "High response time detected" + description: "95th percentile response time is {{ $value }}s" + + - alert: LowThroughput + expr: locust_requests_per_second < 50 + for: 3m + labels: + severity: warning + service: locust + annotations: + summary: "Low throughput detected" + description: "Current RPS is {{ $value }}" + + - alert: SystemResourceHigh + expr: (100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)) > 80 + for: 5m + labels: + severity: critical + service: system + annotations: + summary: "High CPU usage on {{ $labels.instance }}" + description: "CPU usage is {{ $value }}%" +``` + +## 🚨 AlertManager配置 + +### 1. 基础配置 + +```yaml +# alertmanager/alertmanager.yml +global: + smtp_smarthost: 'smtp.gmail.com:587' + smtp_from: 'alerts@yourcompany.com' + smtp_auth_username: 'alerts@yourcompany.com' + smtp_auth_password: 'your-app-password' + +route: + group_by: ['alertname', 'cluster', 'service'] + group_wait: 10s + group_interval: 10s + repeat_interval: 1h + receiver: 'default-receiver' + routes: + - match: + severity: critical + receiver: 'critical-alerts' + group_wait: 5s + repeat_interval: 30m + - match: + severity: warning + receiver: 'warning-alerts' + repeat_interval: 2h + +receivers: + - name: 'default-receiver' + email_configs: + - to: 'team@yourcompany.com' + subject: 'Locust Alert: {{ .GroupLabels.alertname }}' + body: | + {{ range .Alerts }} + Alert: {{ .Annotations.summary }} + Description: {{ .Annotations.description }} + Labels: {{ range .Labels.SortedPairs }}{{ .Name }}={{ .Value }} {{ end }} + {{ end }} + + - name: 'critical-alerts' + email_configs: + - to: 'oncall@yourcompany.com' + subject: 'CRITICAL: Locust Alert' + body: | + CRITICAL ALERT TRIGGERED + + {{ range .Alerts }} + Alert: {{ .Annotations.summary }} + Description: {{ .Annotations.description }} + Severity: {{ .Labels.severity }} + Service: {{ .Labels.service }} + Time: {{ .StartsAt }} + {{ end }} + + slack_configs: + - api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK' + channel: '#alerts' + title: 'Critical Locust Alert' + text: | + {{ range .Alerts }} + *{{ .Annotations.summary }}* + {{ .Annotations.description }} + {{ end }} + color: 'danger' + + - name: 'warning-alerts' + slack_configs: + - api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK' + channel: '#monitoring' + title: 'Locust Warning' + text: | + {{ range .Alerts }} + {{ .Annotations.summary }} + {{ .Annotations.description }} + {{ end }} + color: 'warning' + +inhibit_rules: + - source_match: + severity: 'critical' + target_match: + severity: 'warning' + equal: ['alertname', 'cluster', 'service'] +``` + +### 2. 自定义通知渠道 + +```python +# monitoring/notification_manager.py +import requests +import json +from typing import Dict, List +from datetime import datetime + +class NotificationManager: + """通知管理器""" + + def __init__(self, config: Dict): + self.config = config + self.channels = { + 'slack': self.send_slack_notification, + 'dingtalk': self.send_dingtalk_notification, + 'feishu': self.send_feishu_notification, + 'email': self.send_email_notification, + 'webhook': self.send_webhook_notification + } + + def send_alert(self, alert_data: Dict, channels: List[str] = None): + """发送告警""" + channels = channels or self.config.get('default_channels', ['slack']) + + for channel in channels: + if channel in self.channels: + try: + self.channels[channel](alert_data) + print(f"Alert sent to {channel}") + except Exception as e: + print(f"Failed to send alert to {channel}: {e}") + + def send_slack_notification(self, alert_data: Dict): + """发送Slack通知""" + webhook_url = self.config['slack']['webhook_url'] + + color = 'danger' if alert_data['severity'] == 'critical' else 'warning' + + payload = { + "attachments": [ + { + "color": color, + "title": f"Locust Alert: {alert_data['alert_name']}", + "text": alert_data['description'], + "fields": [ + { + "title": "Severity", + "value": alert_data['severity'], + "short": True + }, + { + "title": "Service", + "value": alert_data.get('service', 'locust'), + "short": True + }, + { + "title": "Time", + "value": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "short": True + } + ] + } + ] + } + + response = requests.post(webhook_url, json=payload, timeout=10) + response.raise_for_status() + + def send_dingtalk_notification(self, alert_data: Dict): + """发送钉钉通知""" + webhook_url = self.config['dingtalk']['webhook_url'] + + message = f""" + 🚨 Locust性能告警 + + **告警名称**: {alert_data['alert_name']} + **严重程度**: {alert_data['severity']} + **描述**: {alert_data['description']} + **时间**: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} + """ + + payload = { + "msgtype": "markdown", + "markdown": { + "title": "Locust性能告警", + "text": message + } + } + + response = requests.post(webhook_url, json=payload, timeout=10) + response.raise_for_status() + + def send_feishu_notification(self, alert_data: Dict): + """发送飞书通知""" + webhook_url = self.config['feishu']['webhook_url'] + + color = "red" if alert_data['severity'] == 'critical' else "orange" + + payload = { + "msg_type": "interactive", + "card": { + "elements": [ + { + "tag": "div", + "text": { + "content": f"**{alert_data['alert_name']}**\n{alert_data['description']}", + "tag": "lark_md" + } + }, + { + "tag": "div", + "fields": [ + { + "is_short": True, + "text": { + "content": f"**严重程度**\n{alert_data['severity']}", + "tag": "lark_md" + } + }, + { + "is_short": True, + "text": { + "content": f"**时间**\n{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", + "tag": "lark_md" + } + } + ] + } + ], + "header": { + "title": { + "content": "Locust性能告警", + "tag": "plain_text" + }, + "template": color + } + } + } + + response = requests.post(webhook_url, json=payload, timeout=10) + response.raise_for_status() + + def send_email_notification(self, alert_data: Dict): + """发送邮件通知""" + # 实现邮件发送逻辑 + pass + + def send_webhook_notification(self, alert_data: Dict): + """发送Webhook通知""" + webhook_url = self.config['webhook']['url'] + + payload = { + "alert_name": alert_data['alert_name'], + "severity": alert_data['severity'], + "description": alert_data['description'], + "timestamp": datetime.now().isoformat(), + "source": "locust-framework" + } + + response = requests.post(webhook_url, json=payload, timeout=10) + response.raise_for_status() + +# 配置示例 +notification_config = { + "default_channels": ["slack", "dingtalk"], + "slack": { + "webhook_url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK" + }, + "dingtalk": { + "webhook_url": "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN" + }, + "feishu": { + "webhook_url": "https://open.feishu.cn/open-apis/bot/v2/hook/YOUR_HOOK" + }, + "webhook": { + "url": "https://your-api.com/alerts" + } +} + +notification_manager = NotificationManager(notification_config) +``` + +## 📋 日志配置 + +### 1. 日志聚合 + +```yaml +# logging/filebeat.yml +filebeat.inputs: +- type: log + enabled: true + paths: + - /app/logs/*.log + fields: + service: locust + environment: production + fields_under_root: true + multiline.pattern: '^\d{4}-\d{2}-\d{2}' + multiline.negate: true + multiline.match: after + +output.elasticsearch: + hosts: ["elasticsearch:9200"] + index: "locust-logs-%{+yyyy.MM.dd}" + template.name: "locust-logs" + template.pattern: "locust-logs-*" + +processors: +- add_host_metadata: + when.not.contains.tags: forwarded +- add_docker_metadata: ~ +``` + +### 2. 日志格式化 + +```python +# logging/log_formatter.py +import logging +import json +from datetime import datetime + +class StructuredFormatter(logging.Formatter): + """结构化日志格式化器""" + + def format(self, record): + log_entry = { + "timestamp": datetime.utcnow().isoformat(), + "level": record.levelname, + "logger": record.name, + "message": record.getMessage(), + "module": record.module, + "function": record.funcName, + "line": record.lineno + } + + # 添加异常信息 + if record.exc_info: + log_entry["exception"] = self.formatException(record.exc_info) + + # 添加自定义字段 + if hasattr(record, 'user_id'): + log_entry["user_id"] = record.user_id + if hasattr(record, 'request_id'): + log_entry["request_id"] = record.request_id + if hasattr(record, 'test_name'): + log_entry["test_name"] = record.test_name + + return json.dumps(log_entry) + +# 配置日志 +def setup_logging(): + """设置日志配置""" + logger = logging.getLogger() + logger.setLevel(logging.INFO) + + # 控制台处理器 + console_handler = logging.StreamHandler() + console_handler.setFormatter(StructuredFormatter()) + logger.addHandler(console_handler) + + # 文件处理器 + file_handler = logging.FileHandler('/app/logs/locust.log') + file_handler.setFormatter(StructuredFormatter()) + logger.addHandler(file_handler) + + return logger +``` + +## 🔧 部署配置 + +### Docker Compose监控栈 + +```yaml +# docker-compose.monitoring.yml +version: '3.8' + +services: + prometheus: + image: prom/prometheus:latest + container_name: prometheus + ports: + - "9090:9090" + volumes: + - ./prometheus:/etc/prometheus + - prometheus-data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/etc/prometheus/console_libraries' + - '--web.console.templates=/etc/prometheus/consoles' + - '--storage.tsdb.retention.time=200h' + - '--web.enable-lifecycle' + networks: + - monitoring + + grafana: + image: grafana/grafana:latest + container_name: grafana + ports: + - "3000:3000" + volumes: + - grafana-data:/var/lib/grafana + - ./grafana/provisioning:/etc/grafana/provisioning + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin123 + - GF_USERS_ALLOW_SIGN_UP=false + networks: + - monitoring + + alertmanager: + image: prom/alertmanager:latest + container_name: alertmanager + ports: + - "9093:9093" + volumes: + - ./alertmanager:/etc/alertmanager + command: + - '--config.file=/etc/alertmanager/alertmanager.yml' + - '--storage.path=/alertmanager' + - '--web.external-url=http://localhost:9093' + networks: + - monitoring + + node-exporter: + image: prom/node-exporter:latest + container_name: node-exporter + ports: + - "9100:9100" + volumes: + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /:/rootfs:ro + command: + - '--path.procfs=/host/proc' + - '--path.rootfs=/rootfs' + - '--path.sysfs=/host/sys' + - '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)' + networks: + - monitoring + +networks: + monitoring: + driver: bridge + +volumes: + prometheus-data: + grafana-data: +``` + +## 🎉 总结 + +完整的监控配置包括: + +1. **指标收集**: Prometheus + 自定义导出器 +2. **可视化**: Grafana仪表板和图表 +3. **告警**: AlertManager + 多渠道通知 +4. **日志**: 结构化日志 + ELK栈 +5. **部署**: Docker化监控栈 + +## 📚 相关文档 + +- [生产环境配置](production.md) - 生产部署配置 +- [通知配置](notification-config.md) - 通知系统配置 +- [故障排除](../examples/troubleshooting.md) - 监控问题排查 +- [最佳实践](../examples/best-practices.md) - 监控最佳实践 diff --git a/docs/configuration/notification-config.md b/docs/configuration/notification-config.md new file mode 100644 index 0000000..81f38b4 --- /dev/null +++ b/docs/configuration/notification-config.md @@ -0,0 +1,798 @@ +# 通知配置 + +本文档详细介绍如何配置Locust性能测试框架的通知系统,包括多种通知渠道的设置、告警规则配置和消息模板定制。 + +## 🎯 通知架构 + +### 通知流程 + +```mermaid +graph TD + A[性能测试] --> B[指标收集] + B --> C[告警规则引擎] + C --> D{触发条件} + D -->|满足| E[通知管理器] + D -->|不满足| F[继续监控] + E --> G[飞书] + E --> H[钉钉] + E --> I[邮件] + E --> J[Slack] + E --> K[微信企业版] + E --> L[自定义Webhook] +``` + +### 支持的通知渠道 + +```yaml +# notification/channels.yml +supported_channels: + instant_messaging: + - feishu # 飞书 + - dingtalk # 钉钉 + - slack # Slack + - wechat_work # 企业微信 + + email: + - smtp # SMTP邮件 + - sendgrid # SendGrid + - ses # AWS SES + + webhook: + - custom_webhook # 自定义Webhook + - teams # Microsoft Teams + - discord # Discord + + sms: + - twilio # Twilio短信 + - aliyun_sms # 阿里云短信 +``` + +## 📱 即时通讯配置 + +### 1. 飞书配置 + +```yaml +# config/notifications.yml +feishu: + enabled: true + webhook_url: "https://open.feishu.cn/open-apis/bot/v2/hook/YOUR_WEBHOOK_TOKEN" + secret: "your_secret_key" # 可选,用于签名验证 + + # 消息模板配置 + templates: + alert: + title: "🚨 Locust性能告警" + color: "red" # red, orange, blue, green, grey + warning: + title: "⚠️ Locust性能警告" + color: "orange" + info: + title: "ℹ️ Locust测试信息" + color: "blue" + success: + title: "✅ Locust测试完成" + color: "green" + + # 发送配置 + settings: + retry_times: 3 + timeout: 10 + rate_limit: 20 # 每分钟最大消息数 +``` + +#### 飞书通知实现 + +```python +# src/plugins/notification/feishu_notifier.py +import requests +import json +import time +import hashlib +import hmac +import base64 +from typing import Dict, Optional +from datetime import datetime + +class FeishuNotifier: + """飞书通知器""" + + def __init__(self, webhook_url: str, secret: Optional[str] = None): + self.webhook_url = webhook_url + self.secret = secret + self.rate_limiter = {} + + def send_message(self, message_type: str, content: Dict, **kwargs) -> bool: + """发送消息""" + try: + # 检查频率限制 + if not self._check_rate_limit(): + print("Rate limit exceeded, message skipped") + return False + + # 构建消息 + payload = self._build_payload(message_type, content, **kwargs) + + # 添加签名(如果配置了secret) + if self.secret: + payload = self._add_signature(payload) + + # 发送请求 + response = requests.post( + self.webhook_url, + json=payload, + timeout=10, + headers={'Content-Type': 'application/json'} + ) + + response.raise_for_status() + result = response.json() + + if result.get('code') == 0: + print("Feishu message sent successfully") + return True + else: + print(f"Feishu API error: {result.get('msg', 'Unknown error')}") + return False + + except Exception as e: + print(f"Failed to send Feishu message: {e}") + return False + + def _build_payload(self, message_type: str, content: Dict, **kwargs) -> Dict: + """构建消息载荷""" + if message_type == "text": + return { + "msg_type": "text", + "content": { + "text": content.get("text", "") + } + } + + elif message_type == "rich_text": + return { + "msg_type": "post", + "content": { + "post": { + "zh_cn": { + "title": content.get("title", ""), + "content": content.get("content", []) + } + } + } + } + + elif message_type == "card": + return { + "msg_type": "interactive", + "card": content + } + + else: + raise ValueError(f"Unsupported message type: {message_type}") + + def _add_signature(self, payload: Dict) -> Dict: + """添加签名验证""" + timestamp = str(int(time.time())) + + # 构建签名字符串 + string_to_sign = f"{timestamp}\n{self.secret}" + + # 计算签名 + hmac_code = hmac.new( + string_to_sign.encode("utf-8"), + digestmod=hashlib.sha256 + ).digest() + sign = base64.b64encode(hmac_code).decode('utf-8') + + # 添加到载荷 + payload["timestamp"] = timestamp + payload["sign"] = sign + + return payload + + def _check_rate_limit(self) -> bool: + """检查频率限制""" + current_minute = int(time.time() // 60) + + if current_minute not in self.rate_limiter: + self.rate_limiter = {current_minute: 1} + return True + + if self.rate_limiter[current_minute] < 20: # 每分钟最多20条 + self.rate_limiter[current_minute] += 1 + return True + + return False + + def send_alert(self, alert_data: Dict) -> bool: + """发送告警消息""" + severity_colors = { + "critical": "red", + "warning": "orange", + "info": "blue" + } + + severity_icons = { + "critical": "🚨", + "warning": "⚠️", + "info": "ℹ️" + } + + severity = alert_data.get("severity", "info") + color = severity_colors.get(severity, "grey") + icon = severity_icons.get(severity, "📊") + + card_content = { + "elements": [ + { + "tag": "div", + "text": { + "content": f"**{alert_data.get('title', '性能告警')}**\n{alert_data.get('description', '')}", + "tag": "lark_md" + } + }, + { + "tag": "div", + "fields": [ + { + "is_short": True, + "text": { + "content": f"**严重程度**\n{severity.upper()}", + "tag": "lark_md" + } + }, + { + "is_short": True, + "text": { + "content": f"**时间**\n{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", + "tag": "lark_md" + } + } + ] + } + ], + "header": { + "title": { + "content": f"{icon} Locust性能告警", + "tag": "plain_text" + }, + "template": color + } + } + + # 添加指标信息 + if "metrics" in alert_data: + metrics_text = [] + for key, value in alert_data["metrics"].items(): + metrics_text.append(f"**{key}**: {value}") + + card_content["elements"].append({ + "tag": "div", + "text": { + "content": "\n".join(metrics_text), + "tag": "lark_md" + } + }) + + # 添加操作按钮 + if alert_data.get("dashboard_url"): + card_content["elements"].append({ + "tag": "action", + "actions": [ + { + "tag": "button", + "text": { + "content": "查看仪表板", + "tag": "plain_text" + }, + "url": alert_data["dashboard_url"], + "type": "primary" + } + ] + }) + + return self.send_message("card", card_content) +``` + +### 2. 钉钉配置 + +```yaml +# config/notifications.yml +dingtalk: + enabled: true + webhook_url: "https://oapi.dingtalk.com/robot/send?access_token=YOUR_ACCESS_TOKEN" + secret: "your_secret_key" # 可选,用于签名验证 + + # 消息配置 + settings: + at_all: false + at_mobiles: [] # @指定手机号 + at_user_ids: [] # @指定用户ID + retry_times: 3 + timeout: 10 +``` + +#### 钉钉通知实现 + +```python +# src/plugins/notification/dingtalk_notifier.py +import requests +import json +import time +import hashlib +import hmac +import base64 +import urllib.parse +from typing import Dict, List, Optional + +class DingTalkNotifier: + """钉钉通知器""" + + def __init__(self, webhook_url: str, secret: Optional[str] = None): + self.webhook_url = webhook_url + self.secret = secret + + def send_message(self, msg_type: str, content: Dict, at_mobiles: List[str] = None, at_all: bool = False) -> bool: + """发送消息""" + try: + # 构建消息载荷 + payload = { + "msgtype": msg_type, + msg_type: content, + "at": { + "atMobiles": at_mobiles or [], + "isAtAll": at_all + } + } + + # 添加签名 + url = self._add_signature() if self.secret else self.webhook_url + + # 发送请求 + response = requests.post( + url, + json=payload, + timeout=10, + headers={'Content-Type': 'application/json'} + ) + + response.raise_for_status() + result = response.json() + + if result.get('errcode') == 0: + print("DingTalk message sent successfully") + return True + else: + print(f"DingTalk API error: {result.get('errmsg', 'Unknown error')}") + return False + + except Exception as e: + print(f"Failed to send DingTalk message: {e}") + return False + + def _add_signature(self) -> str: + """添加签名验证""" + timestamp = str(round(time.time() * 1000)) + secret_enc = self.secret.encode('utf-8') + string_to_sign = f'{timestamp}\n{self.secret}' + string_to_sign_enc = string_to_sign.encode('utf-8') + hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest() + sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) + + return f"{self.webhook_url}×tamp={timestamp}&sign={sign}" + + def send_alert(self, alert_data: Dict) -> bool: + """发送告警消息""" + severity_icons = { + "critical": "🚨", + "warning": "⚠️", + "info": "ℹ️" + } + + severity = alert_data.get("severity", "info") + icon = severity_icons.get(severity, "📊") + + # 构建Markdown消息 + markdown_content = f""" +# {icon} Locust性能告警 + +**告警名称**: {alert_data.get('title', '性能告警')} + +**严重程度**: {severity.upper()} + +**描述**: {alert_data.get('description', '')} + +**时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} +""" + + # 添加指标信息 + if "metrics" in alert_data: + markdown_content += "\n**关键指标**:\n" + for key, value in alert_data["metrics"].items(): + markdown_content += f"- {key}: {value}\n" + + # 添加仪表板链接 + if alert_data.get("dashboard_url"): + markdown_content += f"\n[查看详细仪表板]({alert_data['dashboard_url']})" + + content = { + "title": f"{icon} Locust性能告警", + "text": markdown_content + } + + # 根据严重程度决定是否@所有人 + at_all = severity == "critical" + + return self.send_message("markdown", content, at_all=at_all) +``` + +### 3. Slack配置 + +```yaml +# config/notifications.yml +slack: + enabled: true + webhook_url: "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK" + + # 频道配置 + channels: + alerts: "#alerts" + warnings: "#monitoring" + info: "#general" + + # 消息配置 + settings: + username: "Locust Bot" + icon_emoji: ":robot_face:" + retry_times: 3 + timeout: 10 +``` + +#### Slack通知实现 + +```python +# src/plugins/notification/slack_notifier.py +import requests +import json +from typing import Dict, List, Optional +from datetime import datetime + +class SlackNotifier: + """Slack通知器""" + + def __init__(self, webhook_url: str, default_channel: str = "#general"): + self.webhook_url = webhook_url + self.default_channel = default_channel + + def send_message(self, text: str, channel: str = None, attachments: List[Dict] = None, **kwargs) -> bool: + """发送消息""" + try: + payload = { + "text": text, + "channel": channel or self.default_channel, + "username": kwargs.get("username", "Locust Bot"), + "icon_emoji": kwargs.get("icon_emoji", ":robot_face:") + } + + if attachments: + payload["attachments"] = attachments + + response = requests.post( + self.webhook_url, + json=payload, + timeout=10 + ) + + response.raise_for_status() + + if response.text == "ok": + print("Slack message sent successfully") + return True + else: + print(f"Slack API error: {response.text}") + return False + + except Exception as e: + print(f"Failed to send Slack message: {e}") + return False + + def send_alert(self, alert_data: Dict) -> bool: + """发送告警消息""" + severity_colors = { + "critical": "danger", + "warning": "warning", + "info": "good" + } + + severity_icons = { + "critical": ":rotating_light:", + "warning": ":warning:", + "info": ":information_source:" + } + + severity = alert_data.get("severity", "info") + color = severity_colors.get(severity, "good") + icon = severity_icons.get(severity, ":chart_with_upwards_trend:") + + # 选择频道 + channel_map = { + "critical": "#alerts", + "warning": "#monitoring", + "info": "#general" + } + channel = channel_map.get(severity, self.default_channel) + + # 构建附件 + attachment = { + "color": color, + "title": f"{icon} Locust性能告警", + "text": alert_data.get("description", ""), + "fields": [ + { + "title": "告警名称", + "value": alert_data.get("title", "性能告警"), + "short": True + }, + { + "title": "严重程度", + "value": severity.upper(), + "short": True + }, + { + "title": "时间", + "value": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "short": True + } + ], + "footer": "Locust Framework", + "ts": int(datetime.now().timestamp()) + } + + # 添加指标字段 + if "metrics" in alert_data: + for key, value in alert_data["metrics"].items(): + attachment["fields"].append({ + "title": key, + "value": str(value), + "short": True + }) + + # 添加操作按钮 + if alert_data.get("dashboard_url"): + attachment["actions"] = [ + { + "type": "button", + "text": "查看仪表板", + "url": alert_data["dashboard_url"], + "style": "primary" + } + ] + + return self.send_message( + text=f"{icon} 检测到性能告警", + channel=channel, + attachments=[attachment] + ) +``` + +## 📧 邮件配置 + +### 1. SMTP配置 + +```yaml +# config/notifications.yml +email: + enabled: true + smtp: + host: "smtp.gmail.com" + port: 587 + use_tls: true + username: "your-email@gmail.com" + password: "your-app-password" + + # 发件人配置 + sender: + name: "Locust Performance Monitor" + email: "noreply@yourcompany.com" + + # 收件人配置 + recipients: + critical: ["oncall@yourcompany.com", "team-lead@yourcompany.com"] + warning: ["team@yourcompany.com"] + info: ["monitoring@yourcompany.com"] + + # 模板配置 + templates: + subject: "[{severity}] Locust Alert: {title}" + html_template: "email_alert.html" + text_template: "email_alert.txt" +``` + +#### 邮件通知实现 + +```python +# src/plugins/notification/email_notifier.py +import smtplib +import ssl +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email import encoders +from typing import Dict, List, Optional +from datetime import datetime +import jinja2 + +class EmailNotifier: + """邮件通知器""" + + def __init__(self, smtp_config: Dict): + self.smtp_config = smtp_config + self.template_env = jinja2.Environment( + loader=jinja2.FileSystemLoader('templates/email') + ) + + def send_email(self, to_emails: List[str], subject: str, html_content: str, text_content: str = None) -> bool: + """发送邮件""" + try: + # 创建消息 + message = MIMEMultipart("alternative") + message["Subject"] = subject + message["From"] = self.smtp_config["sender"]["email"] + message["To"] = ", ".join(to_emails) + + # 添加文本内容 + if text_content: + text_part = MIMEText(text_content, "plain") + message.attach(text_part) + + # 添加HTML内容 + html_part = MIMEText(html_content, "html") + message.attach(html_part) + + # 发送邮件 + context = ssl.create_default_context() + + with smtplib.SMTP(self.smtp_config["host"], self.smtp_config["port"]) as server: + if self.smtp_config.get("use_tls"): + server.starttls(context=context) + + server.login( + self.smtp_config["username"], + self.smtp_config["password"] + ) + + server.sendmail( + self.smtp_config["sender"]["email"], + to_emails, + message.as_string() + ) + + print(f"Email sent successfully to {', '.join(to_emails)}") + return True + + except Exception as e: + print(f"Failed to send email: {e}") + return False + + def send_alert(self, alert_data: Dict) -> bool: + """发送告警邮件""" + severity = alert_data.get("severity", "info") + + # 获取收件人列表 + recipients = self.smtp_config["recipients"].get(severity, []) + if not recipients: + print(f"No recipients configured for severity: {severity}") + return False + + # 生成邮件内容 + subject = self.smtp_config["templates"]["subject"].format( + severity=severity.upper(), + title=alert_data.get("title", "性能告警") + ) + + # 渲染HTML模板 + html_template = self.template_env.get_template( + self.smtp_config["templates"]["html_template"] + ) + html_content = html_template.render( + alert_data=alert_data, + timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ) + + # 渲染文本模板 + text_template = self.template_env.get_template( + self.smtp_config["templates"]["text_template"] + ) + text_content = text_template.render( + alert_data=alert_data, + timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ) + + return self.send_email(recipients, subject, html_content, text_content) +``` + +### 2. 邮件模板 + +```html + + + + + + Locust性能告警 + + + +
+
+

🚨 Locust性能告警

+

{{ alert_data.title }}

+
+ +
+

告警详情

+

严重程度: {{ alert_data.severity.upper() }}

+

时间: {{ timestamp }}

+

描述: {{ alert_data.description }}

+ + {% if alert_data.metrics %} +
+

关键指标

+ {% for key, value in alert_data.metrics.items() %} +
+ {{ key }}: + {{ value }} +
+ {% endfor %} +
+ {% endif %} + + {% if alert_data.dashboard_url %} +

+ 查看详细仪表板 +

+ {% endif %} +
+ + +
+ + +``` + +```text + +Locust性能告警 + +告警名称: {{ alert_data.title }} +严重程度: {{ alert_data.severity.upper() }} +时间: {{ timestamp }} +描述: {{ alert_data.description }} + +{% if alert_data.metrics %} +关键指标: +{% for key, value in alert_data.metrics.items() %} +- {{ key }}: {{ value }} +{% endfor %} +{% endif %} + +{% if alert_data.dashboard_url %} +查看详细仪表板: {{ alert_data.dashboard_url }} +{% endif %} + +--- +此邮件由Locust性能测试框架自动发送 +如有问题,请联系运维团队 +``` diff --git a/docs/configuration/plugin-config.md b/docs/configuration/plugin-config.md new file mode 100644 index 0000000..b80a00f --- /dev/null +++ b/docs/configuration/plugin-config.md @@ -0,0 +1,618 @@ +# 插件配置参考 + +本文档详细介绍插件系统的配置选项,包括插件管理、内置插件配置、自定义插件开发配置等。 + +## 🔌 插件系统概述 + +插件系统采用模块化设计,支持动态加载、配置管理、依赖检查等功能: + +```python +# 插件架构层次 +1. 插件管理器 (PluginManager) +2. 插件接口 (PluginInterface) +3. 插件实现 (具体插件类) +4. 插件配置 (配置文件) +``` + +## ⚙️ 插件管理器配置 + +### 基础配置 (conf/plugins/plugin_manager.toml) + +```toml +[plugin_manager] +# 插件发现配置 +discovery_enabled = true +discovery_paths = [ + "src/plugins/builtin", # 内置插件路径 + "plugins/custom", # 自定义插件路径 + "~/.locust/plugins", # 用户插件路径 + "/usr/local/lib/locust/plugins" # 系统插件路径 +] + +# 插件加载配置 +auto_load_enabled = true +load_timeout = 30 +parallel_loading = true +max_load_workers = 4 + +# 插件依赖管理 +dependency_check = true +auto_install_dependencies = false +dependency_timeout = 60 + +# 插件安全配置 +security_check = true +allowed_plugin_sources = ["builtin", "verified", "local"] +signature_verification = false + +# 插件缓存配置 +cache_enabled = true +cache_directory = ".plugin_cache" +cache_ttl = 3600 # 1小时 + +# 插件日志配置 +logging_enabled = true +log_level = "INFO" +log_file = "logs/plugins.log" +``` + +### 插件注册表配置 + +```toml +# conf/plugins/plugin_registry.toml +[registry] +# 注册表配置 +registry_file = "conf/plugins/installed_plugins.json" +auto_register = true +register_on_load = true + +# 插件元数据 +[registry.metadata] +track_usage = true +track_performance = true +track_errors = true + +# 插件版本管理 +[registry.versioning] +version_check = true +compatibility_check = true +auto_update_check = false +``` + +## 🔧 内置插件配置 + +### 性能分析插件 + +```toml +# conf/plugins/performance_analyzer.toml +[performance_analyzer] +enabled = true +priority = 10 + +# 分析配置 +[performance_analyzer.analysis] +# 响应时间阈值配置 (毫秒) +response_time_thresholds = { excellent = 500, good = 1000, acceptable = 2000, poor = 5000 } + +# 吞吐量阈值配置 (TPS) +throughput_thresholds = { excellent = 1000, good = 500, acceptable = 100, poor = 50 } + +# 错误率阈值配置 (百分比) +error_rate_thresholds = { excellent = 0.1, good = 1.0, acceptable = 5.0, poor = 10.0 } + +# 指标权重配置 +metric_weights = { response_time = 0.4, throughput = 0.3, error_rate = 0.2, stability = 0.1 } + +# 报告配置 +[performance_analyzer.reporting] +output_formats = ["html", "json", "pdf"] +include_charts = true +chart_types = ["line", "bar", "pie"] +template_path = "templates/performance_report.html" +output_directory = "reports/performance" + +# 趋势分析配置 +[performance_analyzer.trend] +enabled = true +history_days = 30 +prediction_days = 7 +confidence_level = 0.95 +trend_algorithms = ["linear", "polynomial", "seasonal"] +``` + +### 系统监控插件 + +```toml +# conf/plugins/system_monitor.toml +[system_monitor] +enabled = true +priority = 5 + +# 监控配置 +[system_monitor.monitoring] +interval = 5 # 监控间隔(秒) +buffer_size = 1000 +retention_period = 3600 # 数据保留时间(秒) + +# 监控指标配置 +[system_monitor.metrics] +cpu_enabled = true +memory_enabled = true +disk_enabled = true +network_enabled = true +process_enabled = true + +# CPU监控配置 +[system_monitor.metrics.cpu] +per_cpu = true +load_average = true +context_switches = true + +# 内存监控配置 +[system_monitor.metrics.memory] +virtual_memory = true +swap_memory = true +memory_maps = false + +# 磁盘监控配置 +[system_monitor.metrics.disk] +disk_usage = true +disk_io = true +mount_points = ["/", "/tmp", "/var"] + +# 网络监控配置 +[system_monitor.metrics.network] +network_io = true +connections = true +interface_stats = true +interfaces = ["eth0", "lo"] + +# 告警配置 +[system_monitor.alerts] +cpu_threshold = 80.0 +memory_threshold = 85.0 +disk_threshold = 90.0 +network_threshold = 80.0 +``` + +### 通知服务插件 + +```toml +# conf/plugins/notification_service.toml +[notification_service] +enabled = true +priority = 8 + +# 通知渠道配置 +[notification_service.channels] +enabled_channels = ["feishu", "dingtalk", "email", "wechat_work"] +default_channels = ["feishu", "email"] +fallback_channels = ["email"] + +# 飞书配置 +[notification_service.channels.feishu] +enabled = true +webhook_url = "${FEISHU_WEBHOOK_URL}" +secret = "${FEISHU_SECRET}" +at_all = false +at_users = [] +message_type = "interactive" # text, post, interactive +retry_count = 3 +retry_interval = 5 + +# 钉钉配置 +[notification_service.channels.dingtalk] +enabled = false +webhook_url = "${DINGTALK_WEBHOOK_URL}" +secret = "${DINGTALK_SECRET}" +at_mobiles = [] +at_all = false +retry_count = 3 + +# 邮件配置 +[notification_service.channels.email] +enabled = true +smtp_server = "${EMAIL_SMTP_SERVER}" +smtp_port = 587 +username = "${EMAIL_USERNAME}" +password = "${EMAIL_PASSWORD}" +from_addr = "${EMAIL_FROM_ADDR}" +to_addrs = ["${EMAIL_TO_ADDRS}"] +cc_addrs = [] +bcc_addrs = [] +use_tls = true +use_ssl = false +timeout = 30 + +# 企业微信配置 +[notification_service.channels.wechat_work] +enabled = false +webhook_url = "${WECHAT_WEBHOOK_URL}" +mentioned_list = [] +mentioned_mobile_list = [] + +# 消息模板配置 +[notification_service.templates] +test_start = """ +🚀 性能测试开始 +测试名称: {test_name} +开始时间: {start_time} +目标用户数: {target_users} +测试环境: {environment} +""" + +test_complete = """ +✅ 性能测试完成 +测试名称: {test_name} +结束时间: {end_time} +测试时长: {duration} +总请求数: {total_requests} +成功率: {success_rate}% +平均响应时间: {avg_response_time}ms +报告链接: {report_url} +""" + +alert_critical = """ +🚨 严重告警 +告警名称: {alert_name} +告警级别: {severity} +触发时间: {trigger_time} +告警内容: {message} +当前值: {current_value} +阈值: {threshold} +""" +``` + +### 数据管理插件 + +```toml +# conf/plugins/data_manager.toml +[data_manager] +enabled = true +priority = 7 + +# 数据生成器配置 +[data_manager.generator] +locale = "zh_CN" +seed = 12345 +cache_size = 10000 +cache_ttl = 3600 + +# 数据提供者配置 +[data_manager.provider] +default_strategy = "round_robin" # round_robin, random, sequential +batch_size = 100 +prefetch_enabled = true +prefetch_size = 1000 + +# 数据源配置 +[[data_manager.provider.sources]] +name = "csv_users" +type = "file" +path = "test_data/users.csv" +format = "csv" +encoding = "utf-8" +delimiter = "," + +[[data_manager.provider.sources]] +name = "db_products" +type = "database" +connection = "${DATABASE_URL}" +query = "SELECT * FROM products WHERE active = 1" +cache_enabled = true + +[[data_manager.provider.sources]] +name = "api_orders" +type = "api" +url = "${API_BASE_URL}/orders" +headers = { "Authorization" = "Bearer ${API_TOKEN}" } +method = "GET" +timeout = 30 + +# 数据分发器配置 +[data_manager.distributor] +enabled = true +sync_interval = 60 +batch_size = 1000 +compression = true +encryption = false + +# 分布式配置 +[data_manager.distributor.distributed] +master_node = true +worker_nodes = ["worker1", "worker2", "worker3"] +sync_strategy = "push" # push, pull, hybrid +conflict_resolution = "master_wins" # master_wins, timestamp, merge +``` + +## 🎨 负载形状插件配置 + +```toml +# conf/plugins/load_shapes.toml +[load_shapes] +enabled = true +priority = 6 + +# 默认负载形状配置 +[load_shapes.defaults] +default_shape = "constant" +spawn_rate = 1 +time_limit = 600 + +# 波浪形负载配置 +[load_shapes.wave] +min_users = 10 +max_users = 100 +wave_period = 300 +wave_type = "sine" # sine, cosine, triangle, square + +# 阶梯形负载配置 +[load_shapes.step] +initial_users = 10 +step_size = 20 +step_duration = 60 +max_users = 200 + +# 尖峰负载配置 +[load_shapes.spike] +base_users = 50 +spike_users = 500 +spike_duration = 30 +spike_interval = 300 + +# 自适应负载配置 +[load_shapes.adaptive] +target_response_time = 1000 +adjustment_factor = 0.1 +min_users = 10 +max_users = 1000 +adjustment_interval = 30 +``` + +## 🔍 自定义插件配置 + +### 插件开发配置 + +```toml +# conf/plugins/custom_plugin_template.toml +[custom_plugin] +# 基础信息 +name = "Custom Plugin" +version = "1.0.0" +description = "自定义插件描述" +author = "Your Name" +email = "your.email@example.com" +license = "MIT" +category = "analysis" # analysis, monitoring, data, notification, load_shape + +# 插件配置 +enabled = false +priority = 5 +dependencies = ["requests", "pandas"] +python_version = ">=3.7" + +# 插件特定配置 +[custom_plugin.config] +# 在这里添加插件特定的配置项 +api_endpoint = "https://api.example.com" +api_key = "${CUSTOM_API_KEY}" +timeout = 30 +retry_count = 3 + +# 插件钩子配置 +[custom_plugin.hooks] +# 定义插件响应的事件钩子 +on_test_start = true +on_test_stop = true +on_request = false +on_failure = true +``` + +### 插件验证配置 + +```toml +# conf/plugins/plugin_validation.toml +[validation] +# 插件验证规则 +[validation.rules] +required_methods = ["initialize", "cleanup"] +optional_methods = ["configure", "get_info"] +forbidden_imports = ["os.system", "subprocess.call"] +max_memory_usage = "100MB" +max_cpu_usage = 50.0 + +# 安全检查 +[validation.security] +sandbox_enabled = true +network_access = "restricted" # allowed, restricted, denied +file_access = "restricted" +system_access = "denied" + +# 性能检查 +[validation.performance] +max_init_time = 10.0 +max_cleanup_time = 5.0 +memory_leak_check = true +``` + +## 📊 插件监控配置 + +```toml +# conf/plugins/plugin_monitoring.toml +[monitoring] +# 插件性能监控 +[monitoring.performance] +enabled = true +track_execution_time = true +track_memory_usage = true +track_error_rate = true +alert_on_slow_plugin = true +slow_plugin_threshold = 5.0 # 秒 + +# 插件使用统计 +[monitoring.usage] +enabled = true +track_plugin_calls = true +track_plugin_success_rate = true +generate_usage_report = true +report_interval = 3600 # 1小时 + +# 插件健康检查 +[monitoring.health] +enabled = true +health_check_interval = 300 # 5分钟 +auto_disable_failed_plugins = true +max_consecutive_failures = 3 +``` + +## 🔧 插件配置示例 + +### 完整插件配置示例 + +```python +# plugins/custom/example_plugin.py +from src.plugins.plugin_interface import PluginInterface, PluginInfo +from typing import Dict, Any, Optional + +class ExamplePlugin(PluginInterface): + """示例插件实现""" + + @property + def plugin_info(self) -> PluginInfo: + return PluginInfo( + name="Example Plugin", + version="1.0.0", + description="这是一个示例插件", + author="Your Name", + category="analysis" + ) + + def initialize(self, config: Optional[Dict[str, Any]] = None) -> bool: + """初始化插件""" + self.config = config or {} + self.api_endpoint = self.config.get('api_endpoint', 'https://api.example.com') + self.api_key = self.config.get('api_key') + self.timeout = self.config.get('timeout', 30) + + # 验证必要配置 + if not self.api_key: + self.logger.error("API密钥未配置") + return False + + self.logger.info("示例插件初始化成功") + return True + + def cleanup(self): + """清理资源""" + self.logger.info("示例插件清理完成") + + def process_data(self, data: Dict[str, Any]) -> Dict[str, Any]: + """处理数据""" + # 插件具体逻辑 + result = { + 'processed': True, + 'timestamp': time.time(), + 'data_size': len(str(data)) + } + return result +``` + +### 插件配置文件 + +```toml +# conf/plugins/example_plugin.toml +[example_plugin] +enabled = true +priority = 5 + +[example_plugin.config] +api_endpoint = "https://api.example.com/v1" +api_key = "${EXAMPLE_API_KEY}" +timeout = 30 +retry_count = 3 +batch_size = 100 + +[example_plugin.features] +data_processing = true +real_time_analysis = false +batch_processing = true + +[example_plugin.limits] +max_requests_per_minute = 1000 +max_data_size = "10MB" +max_processing_time = 60 +``` + +## 🚀 插件部署配置 + +### 生产环境配置 + +```toml +# conf/environments/production/plugins.toml +[production.plugins] +# 生产环境只启用稳定插件 +enabled_plugins = [ + "performance_analyzer", + "system_monitor", + "notification_service" +] + +# 禁用开发插件 +disabled_plugins = [ + "debug_plugin", + "test_plugin" +] + +# 严格的安全配置 +[production.plugins.security] +security_check = true +signature_verification = true +sandbox_enabled = true +allowed_plugin_sources = ["builtin", "verified"] + +# 性能优化配置 +[production.plugins.performance] +parallel_loading = true +max_load_workers = 8 +cache_enabled = true +preload_plugins = true +``` + +### 开发环境配置 + +```toml +# conf/environments/development/plugins.toml +[development.plugins] +# 开发环境启用所有插件 +auto_load_enabled = true +discovery_enabled = true + +# 宽松的安全配置 +[development.plugins.security] +security_check = false +signature_verification = false +sandbox_enabled = false +allowed_plugin_sources = ["builtin", "local", "development"] + +# 调试配置 +[development.plugins.debug] +verbose_logging = true +performance_profiling = true +memory_tracking = true +``` + +## ⚠️ 配置注意事项 + +1. **安全性**: 生产环境必须启用安全检查 +2. **性能**: 合理配置插件优先级和并发数 +3. **依赖**: 确保插件依赖正确安装 +4. **版本**: 注意插件版本兼容性 +5. **监控**: 启用插件性能监控 +6. **备份**: 定期备份插件配置 + +## 🔗 相关文档 + +- [插件开发指南](../development/plugin-development.md) - 插件开发详细指南 +- [框架配置](framework-config.md) - 框架核心配置 +- [环境配置](environment-config.md) - 环境特定配置 +- [API参考](../api/plugins.md) - 插件API接口文档 diff --git a/docs/configuration/production.md b/docs/configuration/production.md new file mode 100644 index 0000000..a1aba9e --- /dev/null +++ b/docs/configuration/production.md @@ -0,0 +1,858 @@ +# 生产环境配置 + +本文档详细介绍如何在生产环境中部署和配置Locust性能测试框架,确保测试的稳定性、安全性和可扩展性。 + +## 🎯 生产环境要求 + +### 系统要求 + +**硬件配置** +```yaml +# 推荐配置 +master_node: + cpu: 4+ cores + memory: 8GB+ + disk: 100GB+ SSD + network: 1Gbps+ + +worker_nodes: + cpu: 2+ cores per worker + memory: 4GB+ per worker + disk: 50GB+ SSD + network: 1Gbps+ + +# 最小配置 +minimal_setup: + cpu: 2 cores + memory: 4GB + disk: 50GB + network: 100Mbps +``` + +**软件环境** +```bash +# 操作系统 +Ubuntu 20.04+ / CentOS 8+ / RHEL 8+ + +# Python版本 +Python 3.8+ + +# 必需软件 +- Docker 20.10+ +- Docker Compose 2.0+ +- Nginx 1.18+ +- Redis 6.0+ (可选,用于分布式协调) +- PostgreSQL 13+ (可选,用于结果存储) +``` + +## 🐳 Docker化部署 + +### 1. 生产级Dockerfile + +```dockerfile +# Dockerfile.production +FROM python:3.9-slim + +# 设置工作目录 +WORKDIR /app + +# 安装系统依赖 +RUN apt-get update && apt-get install -y \ + gcc \ + g++ \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# 复制依赖文件 +COPY requirements.txt . +COPY requirements-prod.txt . + +# 安装Python依赖 +RUN pip install --no-cache-dir -r requirements-prod.txt + +# 复制应用代码 +COPY . . + +# 创建非root用户 +RUN useradd -m -u 1000 locust && \ + chown -R locust:locust /app +USER locust + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8089/stats/requests || exit 1 + +# 暴露端口 +EXPOSE 8089 5557 + +# 启动命令 +CMD ["python", "-m", "locust", "--config", "conf/production.conf"] +``` + +### 2. Docker Compose配置 + +```yaml +# docker-compose.prod.yml +version: '3.8' + +services: + locust-master: + build: + context: . + dockerfile: Dockerfile.production + image: locust-framework:production + container_name: locust-master + ports: + - "8089:8089" + - "5557:5557" + environment: + - LOCUST_MODE=master + - LOCUST_MASTER_BIND_HOST=0.0.0.0 + - LOCUST_MASTER_BIND_PORT=5557 + - LOCUST_WEB_HOST=0.0.0.0 + - LOCUST_WEB_PORT=8089 + - LOCUST_LOGLEVEL=INFO + volumes: + - ./conf:/app/conf:ro + - ./logs:/app/logs + - ./reports:/app/reports + networks: + - locust-network + restart: unless-stopped + deploy: + resources: + limits: + cpus: '2.0' + memory: 4G + reservations: + cpus: '1.0' + memory: 2G + + locust-worker: + build: + context: . + dockerfile: Dockerfile.production + image: locust-framework:production + environment: + - LOCUST_MODE=worker + - LOCUST_MASTER_HOST=locust-master + - LOCUST_MASTER_PORT=5557 + - LOCUST_LOGLEVEL=INFO + volumes: + - ./conf:/app/conf:ro + - ./logs:/app/logs + networks: + - locust-network + depends_on: + - locust-master + restart: unless-stopped + deploy: + replicas: 3 + resources: + limits: + cpus: '1.0' + memory: 2G + reservations: + cpus: '0.5' + memory: 1G + + nginx: + image: nginx:alpine + container_name: locust-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + networks: + - locust-network + depends_on: + - locust-master + restart: unless-stopped + + redis: + image: redis:6-alpine + container_name: locust-redis + ports: + - "6379:6379" + volumes: + - redis-data:/data + - ./redis/redis.conf:/etc/redis/redis.conf:ro + networks: + - locust-network + restart: unless-stopped + command: redis-server /etc/redis/redis.conf + + postgres: + image: postgres:13-alpine + container_name: locust-postgres + environment: + - POSTGRES_DB=locust_results + - POSTGRES_USER=locust + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + volumes: + - postgres-data:/var/lib/postgresql/data + - ./postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro + networks: + - locust-network + restart: unless-stopped + +networks: + locust-network: + driver: bridge + +volumes: + redis-data: + postgres-data: +``` + +## ⚙️ 生产配置文件 + +### 1. 主配置文件 + +```ini +# conf/production.conf +[locust] +# 基础配置 +locustfile = locustfiles/production_test.py +host = https://api.production.com +web-host = 0.0.0.0 +web-port = 8089 + +# 性能配置 +users = 1000 +spawn-rate = 50 +run-time = 30m +headless = false + +# 日志配置 +loglevel = INFO +logfile = logs/locust.log + +# 报告配置 +html = reports/performance_report.html +csv = reports/performance_stats + +# 分布式配置 +master-bind-host = 0.0.0.0 +master-bind-port = 5557 +expect-workers = 3 + +# 高级配置 +stop-timeout = 60 +reset-stats = false +``` + +### 2. 环境变量配置 + +```bash +# .env.production +# 应用配置 +LOCUST_ENV=production +LOCUST_LOG_LEVEL=INFO +LOCUST_WEB_AUTH=admin:secure_password_here + +# 数据库配置 +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 +POSTGRES_DB=locust_results +POSTGRES_USER=locust +POSTGRES_PASSWORD=your_secure_password_here + +# Redis配置 +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_PASSWORD=your_redis_password_here + +# 监控配置 +MONITORING_ENABLED=true +ALERT_WEBHOOK_URL=https://hooks.slack.com/your-webhook +METRICS_RETENTION_DAYS=30 + +# 安全配置 +SSL_CERT_PATH=/etc/nginx/ssl/cert.pem +SSL_KEY_PATH=/etc/nginx/ssl/key.pem +ALLOWED_HOSTS=your-domain.com,localhost + +# 性能配置 +MAX_WORKERS=10 +WORKER_MEMORY_LIMIT=2G +MASTER_MEMORY_LIMIT=4G +``` + +### 3. Nginx反向代理配置 + +```nginx +# nginx/nginx.conf +events { + worker_connections 1024; +} + +http { + upstream locust_backend { + server locust-master:8089; + } + + # 限流配置 + limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; + limit_conn_zone $binary_remote_addr zone=conn:10m; + + server { + listen 80; + server_name your-domain.com; + return 301 https://$server_name$request_uri; + } + + server { + listen 443 ssl http2; + server_name your-domain.com; + + # SSL配置 + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512; + ssl_prefer_server_ciphers off; + + # 安全头 + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; + + # 限流 + limit_req zone=api burst=20 nodelay; + limit_conn conn 10; + + # 代理配置 + location / { + proxy_pass http://locust_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket支持 + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 超时配置 + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # 静态文件缓存 + location /static/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # 健康检查 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } +} +``` + +## 🔒 安全配置 + +### 1. 认证和授权 + +```python +# security/auth.py +import hashlib +import secrets +from functools import wraps +from flask import request, Response + +class SecurityManager: + """安全管理器""" + + def __init__(self): + self.api_keys = self.load_api_keys() + self.rate_limits = {} + + def load_api_keys(self): + """加载API密钥""" + return { + "admin": self.hash_password("admin_secure_password"), + "readonly": self.hash_password("readonly_password") + } + + def hash_password(self, password: str) -> str: + """密码哈希""" + salt = secrets.token_hex(16) + return hashlib.pbkdf2_hmac('sha256', + password.encode('utf-8'), + salt.encode('utf-8'), + 100000).hex() + ':' + salt + + def verify_password(self, password: str, hashed: str) -> bool: + """验证密码""" + try: + hash_part, salt = hashed.split(':') + return hashlib.pbkdf2_hmac('sha256', + password.encode('utf-8'), + salt.encode('utf-8'), + 100000).hex() == hash_part + except: + return False + + def require_auth(self, f): + """认证装饰器""" + @wraps(f) + def decorated(*args, **kwargs): + auth = request.authorization + if not auth or not self.verify_credentials(auth.username, auth.password): + return Response('Authentication required', 401, + {'WWW-Authenticate': 'Basic realm="Locust"'}) + return f(*args, **kwargs) + return decorated + + def verify_credentials(self, username: str, password: str) -> bool: + """验证凭据""" + if username in self.api_keys: + return self.verify_password(password, self.api_keys[username]) + return False + +# 使用安全管理器 +security_manager = SecurityManager() + +@security_manager.require_auth +def protected_endpoint(): + return "Protected content" +``` + +### 2. 网络安全配置 + +```yaml +# security/network-policy.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: locust-network-policy +spec: + podSelector: + matchLabels: + app: locust + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + name: monitoring + - podSelector: + matchLabels: + app: nginx + ports: + - protocol: TCP + port: 8089 + - protocol: TCP + port: 5557 + egress: + - to: + - namespaceSelector: + matchLabels: + name: target-system + ports: + - protocol: TCP + port: 443 + - protocol: TCP + port: 80 +``` + +## 📊 监控和告警 + +### 1. Prometheus监控配置 + +```yaml +# monitoring/prometheus.yml +global: + scrape_interval: 15s + evaluation_interval: 15s + +rule_files: + - "locust_rules.yml" + +scrape_configs: + - job_name: 'locust' + static_configs: + - targets: ['locust-master:8089'] + metrics_path: '/stats/requests/csv' + scrape_interval: 30s + + - job_name: 'locust-workers' + static_configs: + - targets: ['locust-worker-1:8089', 'locust-worker-2:8089'] + scrape_interval: 30s + +alerting: + alertmanagers: + - static_configs: + - targets: + - alertmanager:9093 +``` + +### 2. 告警规则 + +```yaml +# monitoring/locust_rules.yml +groups: +- name: locust_alerts + rules: + - alert: HighErrorRate + expr: locust_error_rate > 5 + for: 2m + labels: + severity: warning + annotations: + summary: "High error rate detected" + description: "Error rate is {{ $value }}% for more than 2 minutes" + + - alert: HighResponseTime + expr: locust_avg_response_time > 2000 + for: 5m + labels: + severity: warning + annotations: + summary: "High response time detected" + description: "Average response time is {{ $value }}ms" + + - alert: LowThroughput + expr: locust_rps < 50 + for: 3m + labels: + severity: warning + annotations: + summary: "Low throughput detected" + description: "RPS is {{ $value }}" + + - alert: WorkerDown + expr: up{job="locust-workers"} == 0 + for: 1m + labels: + severity: critical + annotations: + summary: "Locust worker is down" + description: "Worker {{ $labels.instance }} is not responding" +``` + +### 3. Grafana仪表板 + +```json +{ + "dashboard": { + "title": "Locust Performance Dashboard", + "panels": [ + { + "title": "Request Rate", + "type": "graph", + "targets": [ + { + "expr": "rate(locust_requests_total[5m])", + "legendFormat": "RPS" + } + ] + }, + { + "title": "Response Time", + "type": "graph", + "targets": [ + { + "expr": "locust_avg_response_time", + "legendFormat": "Average" + }, + { + "expr": "locust_p95_response_time", + "legendFormat": "95th Percentile" + } + ] + }, + { + "title": "Error Rate", + "type": "singlestat", + "targets": [ + { + "expr": "locust_error_rate", + "legendFormat": "Error Rate %" + } + ] + } + ] + } +} +``` + +## 🚀 部署脚本 + +### 1. 自动化部署脚本 + +```bash +#!/bin/bash +# deploy.sh + +set -e + +# 配置变量 +ENVIRONMENT=${1:-production} +VERSION=${2:-latest} +COMPOSE_FILE="docker-compose.${ENVIRONMENT}.yml" + +echo "Deploying Locust Framework to ${ENVIRONMENT} environment..." + +# 检查环境 +if [ ! -f "${COMPOSE_FILE}" ]; then + echo "Error: ${COMPOSE_FILE} not found" + exit 1 +fi + +# 加载环境变量 +if [ -f ".env.${ENVIRONMENT}" ]; then + source ".env.${ENVIRONMENT}" +fi + +# 构建镜像 +echo "Building Docker images..." +docker-compose -f ${COMPOSE_FILE} build + +# 停止旧服务 +echo "Stopping existing services..." +docker-compose -f ${COMPOSE_FILE} down + +# 启动新服务 +echo "Starting services..." +docker-compose -f ${COMPOSE_FILE} up -d + +# 等待服务启动 +echo "Waiting for services to start..." +sleep 30 + +# 健康检查 +echo "Performing health checks..." +for i in {1..10}; do + if curl -f http://localhost/health > /dev/null 2>&1; then + echo "Health check passed" + break + fi + if [ $i -eq 10 ]; then + echo "Health check failed" + exit 1 + fi + sleep 10 +done + +# 显示状态 +docker-compose -f ${COMPOSE_FILE} ps + +echo "Deployment completed successfully!" +echo "Access the web interface at: https://your-domain.com" +``` + +### 2. 滚动更新脚本 + +```bash +#!/bin/bash +# rolling_update.sh + +set -e + +COMPOSE_FILE="docker-compose.production.yml" +SERVICE_NAME="locust-worker" +NEW_IMAGE="locust-framework:${1:-latest}" + +echo "Performing rolling update for ${SERVICE_NAME}..." + +# 获取当前副本数 +REPLICAS=$(docker-compose -f ${COMPOSE_FILE} ps -q ${SERVICE_NAME} | wc -l) + +echo "Current replicas: ${REPLICAS}" + +# 逐个更新worker +for i in $(seq 1 ${REPLICAS}); do + echo "Updating worker ${i}/${REPLICAS}..." + + # 停止一个worker + CONTAINER_ID=$(docker-compose -f ${COMPOSE_FILE} ps -q ${SERVICE_NAME} | head -n1) + docker stop ${CONTAINER_ID} + + # 启动新的worker + docker-compose -f ${COMPOSE_FILE} up -d --scale ${SERVICE_NAME}=${REPLICAS} + + # 等待新worker就绪 + sleep 30 + + # 验证worker状态 + if ! docker-compose -f ${COMPOSE_FILE} ps ${SERVICE_NAME} | grep -q "Up"; then + echo "Error: Worker failed to start" + exit 1 + fi + + echo "Worker ${i} updated successfully" +done + +echo "Rolling update completed successfully!" +``` + +## 🔧 性能调优 + +### 1. 系统级优化 + +```bash +# system_tuning.sh +#!/bin/bash + +echo "Applying system-level optimizations..." + +# 增加文件描述符限制 +echo "* soft nofile 65536" >> /etc/security/limits.conf +echo "* hard nofile 65536" >> /etc/security/limits.conf + +# 网络优化 +echo "net.core.somaxconn = 65536" >> /etc/sysctl.conf +echo "net.core.netdev_max_backlog = 5000" >> /etc/sysctl.conf +echo "net.ipv4.tcp_max_syn_backlog = 65536" >> /etc/sysctl.conf +echo "net.ipv4.tcp_keepalive_time = 600" >> /etc/sysctl.conf + +# 应用设置 +sysctl -p + +echo "System optimization completed" +``` + +### 2. 应用级优化 + +```python +# optimization/performance_config.py +import os +import multiprocessing + +class ProductionConfig: + """生产环境性能配置""" + + # 进程配置 + WORKER_PROCESSES = min(multiprocessing.cpu_count(), 8) + WORKER_CONNECTIONS = 1000 + WORKER_CLASS = "gevent" + + # 内存配置 + MAX_MEMORY_PER_WORKER = "2G" + PRELOAD_APP = True + + # 网络配置 + KEEPALIVE = 2 + TIMEOUT = 30 + GRACEFUL_TIMEOUT = 30 + + # 日志配置 + ACCESS_LOG = "/app/logs/access.log" + ERROR_LOG = "/app/logs/error.log" + LOG_LEVEL = "info" + + @classmethod + def apply_optimizations(cls): + """应用性能优化""" + # 设置环境变量 + os.environ.update({ + "PYTHONUNBUFFERED": "1", + "PYTHONDONTWRITEBYTECODE": "1", + "GEVENT_RESOLVER": "ares", + "LOCUST_WORKER_PROCESSES": str(cls.WORKER_PROCESSES) + }) + + # 配置垃圾回收 + import gc + gc.set_threshold(700, 10, 10) + + print(f"Applied optimizations: {cls.WORKER_PROCESSES} workers") +``` + +## 📚 运维手册 + +### 1. 日常维护检查清单 + +```markdown +## 日常检查 (每日) +- [ ] 检查服务状态 +- [ ] 查看错误日志 +- [ ] 监控资源使用率 +- [ ] 验证备份完整性 + +## 周检查 +- [ ] 更新安全补丁 +- [ ] 清理旧日志文件 +- [ ] 检查磁盘空间 +- [ ] 性能趋势分析 + +## 月检查 +- [ ] 容量规划评估 +- [ ] 安全审计 +- [ ] 灾难恢复测试 +- [ ] 配置备份验证 +``` + +### 2. 故障处理流程 + +```bash +# troubleshooting.sh +#!/bin/bash + +echo "Locust Framework Troubleshooting Guide" +echo "======================================" + +# 检查服务状态 +echo "1. Checking service status..." +docker-compose ps + +# 检查日志 +echo "2. Checking recent logs..." +docker-compose logs --tail=50 locust-master +docker-compose logs --tail=50 locust-worker + +# 检查资源使用 +echo "3. Checking resource usage..." +docker stats --no-stream + +# 检查网络连接 +echo "4. Checking network connectivity..." +curl -I http://localhost/health + +# 生成诊断报告 +echo "5. Generating diagnostic report..." +{ + echo "=== System Information ===" + uname -a + echo "=== Docker Version ===" + docker version + echo "=== Disk Usage ===" + df -h + echo "=== Memory Usage ===" + free -h +} > diagnostic_report.txt + +echo "Diagnostic report saved to: diagnostic_report.txt" +``` + +## 🎉 总结 + +生产环境部署的关键要点: + +1. **容器化部署**: 使用Docker确保环境一致性 +2. **安全配置**: 实施认证、授权和网络安全 +3. **监控告警**: 建立完善的监控和告警体系 +4. **性能优化**: 系统和应用级别的性能调优 +5. **自动化运维**: 部署、更新和维护的自动化 + +## 📚 相关文档 + +- [框架配置](framework-config.md) - 基础配置说明 +- [监控配置](monitoring-config.md) - 监控系统配置 +- [分布式配置](distributed.md) - 分布式部署配置 +- [最佳实践](../examples/best-practices.md) - 性能测试最佳实践 diff --git a/docs/configuration/testing.md b/docs/configuration/testing.md new file mode 100644 index 0000000..6145bf7 --- /dev/null +++ b/docs/configuration/testing.md @@ -0,0 +1,933 @@ +# 测试环境配置 + +本文档详细介绍如何配置Locust性能测试框架的测试环境,包括测试环境搭建、配置管理和测试数据准备。 + +## 🎯 测试环境架构 + +### 环境分层 + +```yaml +# testing/environment_layers.yml +environment_layers: + unit_testing: + description: "单元测试环境" + scope: "单个组件测试" + isolation: "完全隔离" + dependencies: "最小化" + + integration_testing: + description: "集成测试环境" + scope: "组件间交互测试" + isolation: "部分隔离" + dependencies: "真实服务" + + system_testing: + description: "系统测试环境" + scope: "完整系统测试" + isolation: "接近生产" + dependencies: "完整服务栈" + + performance_testing: + description: "性能测试环境" + scope: "性能和负载测试" + isolation: "独立环境" + dependencies: "生产级配置" +``` + +### 测试环境拓扑 + +```mermaid +graph TD + A[测试控制器] --> B[Locust Master] + B --> C[Locust Worker 1] + B --> D[Locust Worker 2] + B --> E[Locust Worker N] + + C --> F[目标应用集群] + D --> F + E --> F + + F --> G[数据库集群] + F --> H[缓存集群] + F --> I[消息队列] + + J[监控系统] --> B + J --> F + J --> G + J --> H + J --> I + + K[日志收集] --> B + K --> F +``` + +## 🏗️ 基础设施配置 + +### 1. Docker测试环境 + +```yaml +# docker-compose.test.yml +version: '3.8' + +services: + # Locust Master + locust-master: + build: + context: . + dockerfile: Dockerfile.test + container_name: locust-master-test + ports: + - "8089:8089" + - "5557:5557" + environment: + - LOCUST_MODE=master + - LOCUST_MASTER_BIND_HOST=0.0.0.0 + - LOCUST_MASTER_BIND_PORT=5557 + - LOCUST_WEB_HOST=0.0.0.0 + - LOCUST_WEB_PORT=8089 + volumes: + - ./locustfiles:/app/locustfiles + - ./test-data:/app/test-data + - ./logs:/app/logs + networks: + - test-network + depends_on: + - target-app + - redis + - postgres + + # Locust Workers + locust-worker-1: + build: + context: . + dockerfile: Dockerfile.test + container_name: locust-worker-1-test + environment: + - LOCUST_MODE=worker + - LOCUST_MASTER_HOST=locust-master + - LOCUST_MASTER_PORT=5557 + volumes: + - ./locustfiles:/app/locustfiles + - ./test-data:/app/test-data + networks: + - test-network + depends_on: + - locust-master + + locust-worker-2: + build: + context: . + dockerfile: Dockerfile.test + container_name: locust-worker-2-test + environment: + - LOCUST_MODE=worker + - LOCUST_MASTER_HOST=locust-master + - LOCUST_MASTER_PORT=5557 + volumes: + - ./locustfiles:/app/locustfiles + - ./test-data:/app/test-data + networks: + - test-network + depends_on: + - locust-master + + # 目标应用 + target-app: + image: nginx:alpine + container_name: target-app-test + ports: + - "8080:80" + volumes: + - ./test-configs/nginx.conf:/etc/nginx/nginx.conf + - ./test-data/static:/usr/share/nginx/html + networks: + - test-network + + # 数据库 + postgres: + image: postgres:13 + container_name: postgres-test + ports: + - "5432:5432" + environment: + - POSTGRES_DB=testdb + - POSTGRES_USER=testuser + - POSTGRES_PASSWORD=testpass + volumes: + - postgres-test-data:/var/lib/postgresql/data + - ./test-data/sql:/docker-entrypoint-initdb.d + networks: + - test-network + + # 缓存 + redis: + image: redis:alpine + container_name: redis-test + ports: + - "6379:6379" + command: redis-server --appendonly yes + volumes: + - redis-test-data:/data + networks: + - test-network + + # 消息队列 + rabbitmq: + image: rabbitmq:3-management + container_name: rabbitmq-test + ports: + - "5672:5672" + - "15672:15672" + environment: + - RABBITMQ_DEFAULT_USER=testuser + - RABBITMQ_DEFAULT_PASS=testpass + volumes: + - rabbitmq-test-data:/var/lib/rabbitmq + networks: + - test-network + + # 监控 + prometheus: + image: prom/prometheus:latest + container_name: prometheus-test + ports: + - "9090:9090" + volumes: + - ./test-configs/prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus-test-data:/prometheus + networks: + - test-network + +networks: + test-network: + driver: bridge + +volumes: + postgres-test-data: + redis-test-data: + rabbitmq-test-data: + prometheus-test-data: +``` + +### 2. Kubernetes测试环境 + +```yaml +# k8s/test-namespace.yml +apiVersion: v1 +kind: Namespace +metadata: + name: locust-test + labels: + environment: test + purpose: performance-testing + +--- +# k8s/locust-master-test.yml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: locust-master + namespace: locust-test +spec: + replicas: 1 + selector: + matchLabels: + app: locust-master + template: + metadata: + labels: + app: locust-master + spec: + containers: + - name: locust-master + image: locust-framework:test + ports: + - containerPort: 8089 + - containerPort: 5557 + env: + - name: LOCUST_MODE + value: "master" + - name: LOCUST_MASTER_BIND_HOST + value: "0.0.0.0" + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1000m" + volumeMounts: + - name: locustfiles + mountPath: /app/locustfiles + - name: test-data + mountPath: /app/test-data + volumes: + - name: locustfiles + configMap: + name: locustfiles-config + - name: test-data + configMap: + name: test-data-config + +--- +apiVersion: v1 +kind: Service +metadata: + name: locust-master-service + namespace: locust-test +spec: + selector: + app: locust-master + ports: + - name: web + port: 8089 + targetPort: 8089 + - name: master + port: 5557 + targetPort: 5557 + type: LoadBalancer + +--- +# k8s/locust-worker-test.yml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: locust-worker + namespace: locust-test +spec: + replicas: 3 + selector: + matchLabels: + app: locust-worker + template: + metadata: + labels: + app: locust-worker + spec: + containers: + - name: locust-worker + image: locust-framework:test + env: + - name: LOCUST_MODE + value: "worker" + - name: LOCUST_MASTER_HOST + value: "locust-master-service" + - name: LOCUST_MASTER_PORT + value: "5557" + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "500m" + volumeMounts: + - name: locustfiles + mountPath: /app/locustfiles + - name: test-data + mountPath: /app/test-data + volumes: + - name: locustfiles + configMap: + name: locustfiles-config + - name: test-data + configMap: + name: test-data-config +``` + +## ⚙️ 配置管理 + +### 1. 环境配置文件 + +```yaml +# config/test.yml +# 测试环境配置 +environment: + name: "test" + description: "Performance testing environment" + +# Locust配置 +locust: + host: "http://target-app:8080" + users: 100 + spawn_rate: 10 + run_time: "300s" + + # Web UI配置 + web: + host: "0.0.0.0" + port: 8089 + auth_credentials: "admin:test123" + + # Master配置 + master: + bind_host: "0.0.0.0" + bind_port: 5557 + expect_workers: 2 + expect_workers_max_wait: 60 + + # Worker配置 + worker: + master_host: "locust-master" + master_port: 5557 + +# 目标系统配置 +target_system: + base_url: "http://target-app:8080" + api_version: "v1" + timeout: 30 + retries: 3 + + # 认证配置 + auth: + type: "bearer_token" + token_url: "/auth/token" + username: "testuser" + password: "testpass" + + # 端点配置 + endpoints: + health: "/health" + login: "/auth/login" + logout: "/auth/logout" + users: "/api/v1/users" + orders: "/api/v1/orders" + products: "/api/v1/products" + +# 数据库配置 +database: + host: "postgres" + port: 5432 + name: "testdb" + username: "testuser" + password: "testpass" + pool_size: 10 + max_overflow: 20 + +# 缓存配置 +cache: + host: "redis" + port: 6379 + db: 0 + password: "" + timeout: 5 + +# 消息队列配置 +message_queue: + host: "rabbitmq" + port: 5672 + username: "testuser" + password: "testpass" + virtual_host: "/" + +# 监控配置 +monitoring: + prometheus: + host: "prometheus" + port: 9090 + scrape_interval: "15s" + + metrics: + enabled: true + port: 9090 + path: "/metrics" + + logging: + level: "INFO" + format: "json" + file: "/app/logs/locust-test.log" + +# 通知配置 +notifications: + enabled: true + channels: + - type: "slack" + webhook_url: "https://hooks.slack.com/test-webhook" + channel: "#testing" + - type: "email" + smtp_host: "smtp.test.com" + recipients: ["test-team@company.com"] + +# 测试数据配置 +test_data: + users: + count: 1000 + file: "/app/test-data/users.json" + + products: + count: 500 + file: "/app/test-data/products.json" + + orders: + count: 2000 + file: "/app/test-data/orders.json" + + # 数据生成配置 + generation: + locale: "zh_CN" + seed: 12345 + batch_size: 100 +``` + +### 2. 环境变量配置 + +```bash +# .env.test +# 基础环境配置 +ENVIRONMENT=test +DEBUG=true +LOG_LEVEL=INFO + +# Locust配置 +LOCUST_HOST=http://target-app:8080 +LOCUST_USERS=100 +LOCUST_SPAWN_RATE=10 +LOCUST_RUN_TIME=300s +LOCUST_WEB_HOST=0.0.0.0 +LOCUST_WEB_PORT=8089 + +# 数据库配置 +DATABASE_URL=postgresql://testuser:testpass@postgres:5432/testdb +DATABASE_POOL_SIZE=10 +DATABASE_MAX_OVERFLOW=20 + +# 缓存配置 +REDIS_URL=redis://redis:6379/0 +REDIS_TIMEOUT=5 + +# 消息队列配置 +RABBITMQ_URL=amqp://testuser:testpass@rabbitmq:5672/ +RABBITMQ_EXCHANGE=locust_test + +# 监控配置 +PROMETHEUS_URL=http://prometheus:9090 +METRICS_ENABLED=true +METRICS_PORT=9090 + +# 测试配置 +TEST_DATA_DIR=/app/test-data +TEST_RESULTS_DIR=/app/test-results +TEST_TIMEOUT=3600 + +# 安全配置 +SECRET_KEY=test-secret-key-change-in-production +JWT_SECRET=test-jwt-secret +API_KEY=test-api-key + +# 第三方服务配置 +SLACK_WEBHOOK_URL=https://hooks.slack.com/test-webhook +EMAIL_SMTP_HOST=smtp.test.com +EMAIL_SMTP_PORT=587 +EMAIL_USERNAME=test@company.com +EMAIL_PASSWORD=test-password +``` + +## 📊 测试数据管理 + +### 1. 测试数据生成 + +```python +# test_data/data_generator.py +import json +import random +from faker import Faker +from datetime import datetime, timedelta +from pathlib import Path + +class TestDataGenerator: + """测试数据生成器""" + + def __init__(self, locale='zh_CN', seed=12345): + self.fake = Faker(locale) + Faker.seed(seed) + random.seed(seed) + self.output_dir = Path("test-data") + self.output_dir.mkdir(exist_ok=True) + + def generate_users(self, count=1000): + """生成用户数据""" + users = [] + + for i in range(count): + user = { + "id": i + 1, + "username": self.fake.user_name(), + "email": self.fake.email(), + "first_name": self.fake.first_name(), + "last_name": self.fake.last_name(), + "phone": self.fake.phone_number(), + "address": { + "street": self.fake.street_address(), + "city": self.fake.city(), + "state": self.fake.state(), + "zip_code": self.fake.postcode(), + "country": "中国" + }, + "profile": { + "age": random.randint(18, 80), + "gender": random.choice(["male", "female"]), + "occupation": self.fake.job(), + "company": self.fake.company() + }, + "preferences": { + "language": random.choice(["zh", "en"]), + "currency": "CNY", + "timezone": "Asia/Shanghai" + }, + "created_at": self.fake.date_time_between( + start_date="-2y", end_date="now" + ).isoformat(), + "is_active": random.choice([True, True, True, False]), # 75%活跃 + "last_login": self.fake.date_time_between( + start_date="-30d", end_date="now" + ).isoformat() + } + users.append(user) + + # 保存到文件 + with open(self.output_dir / "users.json", "w", encoding="utf-8") as f: + json.dump(users, f, ensure_ascii=False, indent=2) + + print(f"Generated {count} users") + return users + + def generate_products(self, count=500): + """生成产品数据""" + categories = ["电子产品", "服装", "家居", "图书", "运动", "美妆", "食品"] + products = [] + + for i in range(count): + category = random.choice(categories) + product = { + "id": i + 1, + "name": self.fake.catch_phrase(), + "description": self.fake.text(max_nb_chars=200), + "category": category, + "price": round(random.uniform(10, 1000), 2), + "currency": "CNY", + "stock": random.randint(0, 1000), + "sku": f"SKU{i+1:06d}", + "brand": self.fake.company(), + "specifications": { + "weight": f"{random.uniform(0.1, 10):.2f}kg", + "dimensions": f"{random.randint(10, 100)}x{random.randint(10, 100)}x{random.randint(5, 50)}cm", + "color": self.fake.color_name(), + "material": random.choice(["塑料", "金属", "木材", "布料", "玻璃"]) + }, + "ratings": { + "average": round(random.uniform(3.0, 5.0), 1), + "count": random.randint(0, 1000) + }, + "images": [ + f"https://example.com/images/product_{i+1}_1.jpg", + f"https://example.com/images/product_{i+1}_2.jpg" + ], + "created_at": self.fake.date_time_between( + start_date="-1y", end_date="now" + ).isoformat(), + "is_available": random.choice([True, True, True, False]) # 75%可用 + } + products.append(product) + + # 保存到文件 + with open(self.output_dir / "products.json", "w", encoding="utf-8") as f: + json.dump(products, f, ensure_ascii=False, indent=2) + + print(f"Generated {count} products") + return products + + def generate_orders(self, count=2000, users=None, products=None): + """生成订单数据""" + if not users: + with open(self.output_dir / "users.json", "r", encoding="utf-8") as f: + users = json.load(f) + + if not products: + with open(self.output_dir / "products.json", "r", encoding="utf-8") as f: + products = json.load(f) + + orders = [] + statuses = ["pending", "confirmed", "shipped", "delivered", "cancelled"] + + for i in range(count): + user = random.choice(users) + order_products = random.sample(products, random.randint(1, 5)) + + total_amount = 0 + order_items = [] + + for product in order_products: + quantity = random.randint(1, 3) + item_total = product["price"] * quantity + total_amount += item_total + + order_items.append({ + "product_id": product["id"], + "product_name": product["name"], + "price": product["price"], + "quantity": quantity, + "total": item_total + }) + + order = { + "id": i + 1, + "user_id": user["id"], + "user_email": user["email"], + "items": order_items, + "total_amount": round(total_amount, 2), + "currency": "CNY", + "status": random.choice(statuses), + "payment": { + "method": random.choice(["credit_card", "alipay", "wechat_pay", "bank_transfer"]), + "status": random.choice(["pending", "completed", "failed"]), + "transaction_id": f"TXN{i+1:08d}" + }, + "shipping": { + "address": user["address"], + "method": random.choice(["standard", "express", "overnight"]), + "cost": round(random.uniform(5, 50), 2), + "tracking_number": f"TRK{i+1:010d}" + }, + "created_at": self.fake.date_time_between( + start_date="-6m", end_date="now" + ).isoformat(), + "updated_at": self.fake.date_time_between( + start_date="-6m", end_date="now" + ).isoformat() + } + orders.append(order) + + # 保存到文件 + with open(self.output_dir / "orders.json", "w", encoding="utf-8") as f: + json.dump(orders, f, ensure_ascii=False, indent=2) + + print(f"Generated {count} orders") + return orders + + def generate_all(self, users_count=1000, products_count=500, orders_count=2000): + """生成所有测试数据""" + print("Generating test data...") + + users = self.generate_users(users_count) + products = self.generate_products(products_count) + orders = self.generate_orders(orders_count, users, products) + + # 生成汇总信息 + summary = { + "generated_at": datetime.now().isoformat(), + "counts": { + "users": len(users), + "products": len(products), + "orders": len(orders) + }, + "files": { + "users": "users.json", + "products": "products.json", + "orders": "orders.json" + } + } + + with open(self.output_dir / "summary.json", "w", encoding="utf-8") as f: + json.dump(summary, f, ensure_ascii=False, indent=2) + + print("Test data generation completed!") + return summary + +# 使用示例 +if __name__ == "__main__": + generator = TestDataGenerator() + generator.generate_all() +``` + +### 2. 数据库初始化 + +```sql +-- test-data/sql/01_init_schema.sql +-- 创建测试数据库架构 + +-- 用户表 +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + email VARCHAR(100) UNIQUE NOT NULL, + first_name VARCHAR(50), + last_name VARCHAR(50), + phone VARCHAR(20), + address JSONB, + profile JSONB, + preferences JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + is_active BOOLEAN DEFAULT TRUE, + last_login TIMESTAMP +); + +-- 产品表 +CREATE TABLE IF NOT EXISTS products ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + description TEXT, + category VARCHAR(50), + price DECIMAL(10,2), + currency VARCHAR(3) DEFAULT 'CNY', + stock INTEGER DEFAULT 0, + sku VARCHAR(50) UNIQUE, + brand VARCHAR(100), + specifications JSONB, + ratings JSONB, + images JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + is_available BOOLEAN DEFAULT TRUE +); + +-- 订单表 +CREATE TABLE IF NOT EXISTS orders ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id), + total_amount DECIMAL(10,2), + currency VARCHAR(3) DEFAULT 'CNY', + status VARCHAR(20), + payment JSONB, + shipping JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 订单项表 +CREATE TABLE IF NOT EXISTS order_items ( + id SERIAL PRIMARY KEY, + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + quantity INTEGER, + price DECIMAL(10,2), + total DECIMAL(10,2) +); + +-- 创建索引 +CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); +CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); +CREATE INDEX IF NOT EXISTS idx_products_category ON products(category); +CREATE INDEX IF NOT EXISTS idx_products_sku ON products(sku); +CREATE INDEX IF NOT EXISTS idx_orders_user_id ON orders(user_id); +CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status); +CREATE INDEX IF NOT EXISTS idx_orders_created_at ON orders(created_at); +CREATE INDEX IF NOT EXISTS idx_order_items_order_id ON order_items(order_id); +CREATE INDEX IF NOT EXISTS idx_order_items_product_id ON order_items(product_id); +``` + +```python +# test-data/sql/02_load_data.py +import json +import psycopg2 +from pathlib import Path + +def load_test_data(): + """加载测试数据到数据库""" + + # 数据库连接 + conn = psycopg2.connect( + host="postgres", + port=5432, + database="testdb", + user="testuser", + password="testpass" + ) + + cur = conn.cursor() + + try: + # 加载用户数据 + with open("/app/test-data/users.json", "r", encoding="utf-8") as f: + users = json.load(f) + + for user in users: + cur.execute(""" + INSERT INTO users ( + id, username, email, first_name, last_name, phone, + address, profile, preferences, created_at, is_active, last_login + ) VALUES ( + %(id)s, %(username)s, %(email)s, %(first_name)s, %(last_name)s, %(phone)s, + %(address)s, %(profile)s, %(preferences)s, %(created_at)s, %(is_active)s, %(last_login)s + ) ON CONFLICT (id) DO NOTHING + """, user) + + print(f"Loaded {len(users)} users") + + # 加载产品数据 + with open("/app/test-data/products.json", "r", encoding="utf-8") as f: + products = json.load(f) + + for product in products: + cur.execute(""" + INSERT INTO products ( + id, name, description, category, price, currency, stock, + sku, brand, specifications, ratings, images, created_at, is_available + ) VALUES ( + %(id)s, %(name)s, %(description)s, %(category)s, %(price)s, %(currency)s, %(stock)s, + %(sku)s, %(brand)s, %(specifications)s, %(ratings)s, %(images)s, %(created_at)s, %(is_available)s + ) ON CONFLICT (id) DO NOTHING + """, product) + + print(f"Loaded {len(products)} products") + + # 加载订单数据 + with open("/app/test-data/orders.json", "r", encoding="utf-8") as f: + orders = json.load(f) + + for order in orders: + # 插入订单 + cur.execute(""" + INSERT INTO orders ( + id, user_id, total_amount, currency, status, + payment, shipping, created_at, updated_at + ) VALUES ( + %(id)s, %(user_id)s, %(total_amount)s, %(currency)s, %(status)s, + %(payment)s, %(shipping)s, %(created_at)s, %(updated_at)s + ) ON CONFLICT (id) DO NOTHING + """, { + "id": order["id"], + "user_id": order["user_id"], + "total_amount": order["total_amount"], + "currency": order["currency"], + "status": order["status"], + "payment": json.dumps(order["payment"]), + "shipping": json.dumps(order["shipping"]), + "created_at": order["created_at"], + "updated_at": order["updated_at"] + }) + + # 插入订单项 + for item in order["items"]: + cur.execute(""" + INSERT INTO order_items ( + order_id, product_id, quantity, price, total + ) VALUES ( + %(order_id)s, %(product_id)s, %(quantity)s, %(price)s, %(total)s + ) + """, { + "order_id": order["id"], + "product_id": item["product_id"], + "quantity": item["quantity"], + "price": item["price"], + "total": item["total"] + }) + + print(f"Loaded {len(orders)} orders") + + # 提交事务 + conn.commit() + print("Test data loaded successfully!") + + except Exception as e: + conn.rollback() + print(f"Error loading test data: {e}") + raise + + finally: + cur.close() + conn.close() + +if __name__ == "__main__": + load_test_data() +``` diff --git a/docs/development/coding-standards.md b/docs/development/coding-standards.md new file mode 100644 index 0000000..c5ad0d5 --- /dev/null +++ b/docs/development/coding-standards.md @@ -0,0 +1,781 @@ +# 编码规范 + +本文档定义了Locust性能测试框架的编码规范,确保代码质量、可读性和可维护性。 + +## 🎯 总体原则 + +### 设计原则 + +1. **SOLID原则** + - **单一职责原则(SRP)**: 每个类只负责一个功能 + - **开闭原则(OCP)**: 对扩展开放,对修改关闭 + - **里氏替换原则(LSP)**: 子类可以替换父类 + - **接口隔离原则(ISP)**: 接口应该小而专一 + - **依赖倒置原则(DIP)**: 依赖抽象而非具体实现 + +2. **代码质量原则** + - **DRY (Don't Repeat Yourself)**: 避免重复代码 + - **KISS (Keep It Simple, Stupid)**: 保持简单 + - **YAGNI (You Aren't Gonna Need It)**: 不要过度设计 + +## 📝 Python编码规范 + +### 代码风格 + +遵循 **PEP 8** 标准,使用 **Black** 进行代码格式化。 + +#### 命名规范 + +```python +# 模块名:小写字母,下划线分隔 +# ✅ 正确 +performance_analyzer.py +data_manager.py + +# ❌ 错误 +PerformanceAnalyzer.py +dataManager.py + +# 类名:大驼峰命名法 +# ✅ 正确 +class PerformanceAnalyzer: + pass + +class DataProvider: + pass + +# ❌ 错误 +class performance_analyzer: + pass + +class dataProvider: + pass + +# 函数名和变量名:小写字母,下划线分隔 +# ✅ 正确 +def analyze_performance(): + pass + +user_count = 100 +response_time = 500.0 + +# ❌ 错误 +def analyzePerformance(): + pass + +userCount = 100 +responseTime = 500.0 + +# 常量:大写字母,下划线分隔 +# ✅ 正确 +MAX_USERS = 1000 +DEFAULT_TIMEOUT = 30 +API_VERSION = "v1" + +# 私有属性/方法:单下划线前缀 +class MyClass: + def __init__(self): + self._private_var = "private" + + def _private_method(self): + pass + +# 特殊方法:双下划线前后 +class MyClass: + def __init__(self): + pass + + def __str__(self): + return "MyClass instance" +``` + +#### 导入规范 + +```python +# 导入顺序:标准库 -> 第三方库 -> 本地模块 +# 每组之间空一行 + +# 标准库 +import os +import sys +import time +from typing import Dict, List, Optional, Any + +# 第三方库 +import requests +from locust import HttpUser, task +from dynaconf import Dynaconf + +# 本地模块 +from src.analysis.performance_analyzer import PerformanceAnalyzer +from src.monitoring.alert_manager import AlertManager +from .utils import helper_function + +# ✅ 正确的导入方式 +from typing import Dict, List, Optional + +# ❌ 避免使用通配符导入 +from module import * + +# ✅ 使用别名简化长模块名 +import src.very_long_module_name as vlmn + +# ✅ 相对导入用于同包内模块 +from .sibling_module import function +from ..parent_module import ParentClass +``` + +#### 字符串格式化 + +```python +# ✅ 推荐使用 f-string (Python 3.6+) +name = "张三" +age = 25 +message = f"用户 {name} 年龄 {age} 岁" + +# ✅ 复杂格式化使用 format() +template = "响应时间: {time:.2f}ms, 状态: {status}" +result = template.format(time=response_time, status="success") + +# ❌ 避免使用 % 格式化 +message = "用户 %s 年龄 %d 岁" % (name, age) + +# ✅ 多行字符串 +sql_query = """ +SELECT user_id, username, email +FROM users +WHERE created_at > %s +ORDER BY created_at DESC +""" + +# ✅ 长字符串拼接 +long_message = ( + "这是一个很长的消息," + "需要分成多行来提高可读性," + "每行不超过88个字符。" +) +``` + +### 文档字符串规范 + +使用 **Google风格** 的文档字符串。 + +```python +def analyze_performance(test_data: Dict[str, Any], + config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """分析测试性能数据 + + 对测试数据进行综合性能分析,包括响应时间、吞吐量、错误率等指标, + 并生成性能评级和改进建议。 + + Args: + test_data: 测试数据字典,包含以下字段: + - requests: 请求列表 + - duration: 测试持续时间 + - users: 用户数量 + config: 分析配置参数,可选 + - thresholds: 性能阈值设置 + - weights: 指标权重配置 + + Returns: + Dict[str, Any]: 分析结果字典,包含: + - overall_grade: 总体评级 (A/B/C/D) + - response_time: 响应时间分析 + - throughput: 吞吐量分析 + - error_analysis: 错误分析 + - recommendations: 改进建议 + + Raises: + ValueError: 当test_data格式不正确时 + AnalysisError: 当分析过程出现错误时 + + Example: + >>> test_data = { + ... 'requests': [{'response_time': 100, 'success': True}], + ... 'duration': 60, + ... 'users': 10 + ... } + >>> result = analyze_performance(test_data) + >>> print(result['overall_grade']) + 'A' + + Note: + 此函数会消耗较多CPU资源,建议在测试结束后调用。 + + Todo: + - 添加更多性能指标 + - 支持自定义评级算法 + """ + pass + +class PerformanceAnalyzer: + """性能分析器 + + 提供全面的性能测试数据分析功能,包括统计分析、 + 趋势分析、异常检测等。 + + Attributes: + config: 分析器配置 + thresholds: 性能阈值设置 + + Example: + >>> analyzer = PerformanceAnalyzer() + >>> result = analyzer.analyze(test_data) + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None): + """初始化性能分析器 + + Args: + config: 分析器配置,包含阈值和权重设置 + """ + pass +``` + +### 类型注解规范 + +```python +from typing import Dict, List, Optional, Union, Tuple, Any, Callable + +# ✅ 基础类型注解 +def process_data(data: str) -> int: + return len(data) + +# ✅ 复合类型注解 +def analyze_requests(requests: List[Dict[str, Any]]) -> Dict[str, float]: + pass + +# ✅ 可选参数 +def create_user(name: str, age: Optional[int] = None) -> Dict[str, Any]: + pass + +# ✅ 联合类型 +def parse_value(value: Union[str, int, float]) -> str: + return str(value) + +# ✅ 回调函数类型 +def register_callback(callback: Callable[[str], None]) -> None: + pass + +# ✅ 类属性注解 +class DataManager: + config: Dict[str, Any] + providers: List[str] + + def __init__(self): + self.config = {} + self.providers = [] + +# ✅ 泛型类型 +from typing import TypeVar, Generic + +T = TypeVar('T') + +class Container(Generic[T]): + def __init__(self, value: T) -> None: + self.value = value + + def get(self) -> T: + return self.value +``` + +### 异常处理规范 + +```python +# ✅ 自定义异常类 +class FrameworkError(Exception): + """框架基础异常类""" + pass + +class AnalysisError(FrameworkError): + """分析相关异常""" + pass + +class ConfigurationError(FrameworkError): + """配置相关异常""" + pass + +# ✅ 异常处理最佳实践 +def load_config(config_path: str) -> Dict[str, Any]: + """加载配置文件""" + try: + with open(config_path, 'r', encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + raise ConfigurationError(f"配置文件不存在: {config_path}") + except json.JSONDecodeError as e: + raise ConfigurationError(f"配置文件格式错误: {e}") + except Exception as e: + # 记录未预期的异常 + logger.exception(f"加载配置文件时发生未知错误: {e}") + raise + +# ✅ 使用上下文管理器 +class DatabaseConnection: + def __enter__(self): + self.connection = create_connection() + return self.connection + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.connection: + self.connection.close() + +# 使用方式 +with DatabaseConnection() as conn: + # 使用连接 + pass + +# ✅ 异常链 +def process_data(data: str) -> Dict[str, Any]: + try: + return parse_json(data) + except ValueError as e: + raise AnalysisError("数据解析失败") from e +``` + +### 日志规范 + +```python +import logging + +# ✅ 日志配置 +logger = logging.getLogger(__name__) + +class PerformanceAnalyzer: + def __init__(self): + self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}") + + def analyze(self, data: Dict[str, Any]) -> Dict[str, Any]: + """分析性能数据""" + self.logger.info("开始性能分析") + self.logger.debug(f"输入数据: {data}") + + try: + result = self._do_analysis(data) + self.logger.info(f"分析完成,评级: {result.get('grade')}") + return result + except Exception as e: + self.logger.error(f"分析失败: {e}", exc_info=True) + raise + +# ✅ 日志级别使用 +logger.debug("详细的调试信息") # 开发调试 +logger.info("一般信息") # 正常流程 +logger.warning("警告信息") # 潜在问题 +logger.error("错误信息") # 错误但程序可继续 +logger.critical("严重错误") # 严重错误,程序可能停止 + +# ✅ 结构化日志 +logger.info("用户登录", extra={ + 'user_id': user_id, + 'ip_address': request.remote_addr, + 'user_agent': request.headers.get('User-Agent') +}) +``` + +## 🏗️ 架构规范 + +### 目录结构 + +``` +src/ +├── analysis/ # 性能分析模块 +│ ├── __init__.py +│ ├── performance_analyzer.py +│ ├── trend_analyzer.py +│ └── report_generator.py +├── monitoring/ # 监控告警模块 +│ ├── __init__.py +│ ├── performance_monitor.py +│ ├── alert_manager.py +│ └── notification_service.py +├── data_manager/ # 数据管理模块 +│ ├── __init__.py +│ ├── data_generator.py +│ ├── data_provider.py +│ └── data_distributor.py +├── plugins/ # 插件系统 +│ ├── __init__.py +│ ├── plugin_interface.py +│ ├── plugin_manager.py +│ └── builtin/ # 内置插件 +└── utils/ # 工具模块 + ├── __init__.py + ├── helpers.py + └── decorators.py +``` + +### 模块设计规范 + +```python +# ✅ 接口定义 +from abc import ABC, abstractmethod +from typing import Protocol + +class AnalyzerInterface(ABC): + """分析器接口""" + + @abstractmethod + def analyze(self, data: Dict[str, Any]) -> Dict[str, Any]: + """分析数据""" + pass + +# ✅ 使用Protocol定义结构化类型 +class Configurable(Protocol): + """可配置对象协议""" + + def configure(self, config: Dict[str, Any]) -> None: + """配置对象""" + ... + +# ✅ 工厂模式 +class AnalyzerFactory: + """分析器工厂""" + + _analyzers = { + 'performance': PerformanceAnalyzer, + 'trend': TrendAnalyzer, + } + + @classmethod + def create_analyzer(cls, analyzer_type: str, **kwargs) -> AnalyzerInterface: + """创建分析器实例""" + if analyzer_type not in cls._analyzers: + raise ValueError(f"未知的分析器类型: {analyzer_type}") + + analyzer_class = cls._analyzers[analyzer_type] + return analyzer_class(**kwargs) + +# ✅ 单例模式 +class ConfigManager: + """配置管理器单例""" + + _instance = None + _config = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def load_config(self, config_path: str) -> None: + """加载配置""" + if self._config is None: + self._config = self._load_from_file(config_path) + + def get_config(self) -> Dict[str, Any]: + """获取配置""" + return self._config or {} +``` + +### 依赖注入 + +```python +# ✅ 依赖注入示例 +class PerformanceAnalyzer: + def __init__(self, + data_provider: DataProviderInterface, + config_manager: ConfigManagerInterface, + logger: logging.Logger): + self.data_provider = data_provider + self.config_manager = config_manager + self.logger = logger + + def analyze(self, test_id: str) -> Dict[str, Any]: + """分析性能数据""" + # 使用注入的依赖 + data = self.data_provider.get_test_data(test_id) + config = self.config_manager.get_analysis_config() + + self.logger.info(f"开始分析测试 {test_id}") + # 执行分析逻辑 + return result + +# ✅ 依赖容器 +class DIContainer: + """依赖注入容器""" + + def __init__(self): + self._services = {} + self._singletons = {} + + def register(self, interface: type, implementation: type, singleton: bool = False): + """注册服务""" + self._services[interface] = (implementation, singleton) + + def get(self, interface: type): + """获取服务实例""" + if interface in self._singletons: + return self._singletons[interface] + + implementation, is_singleton = self._services[interface] + instance = implementation() + + if is_singleton: + self._singletons[interface] = instance + + return instance +``` + +## 🧪 测试规范 + +### 单元测试 + +```python +import pytest +from unittest.mock import Mock, patch +from src.analysis.performance_analyzer import PerformanceAnalyzer + +class TestPerformanceAnalyzer: + """性能分析器测试类""" + + def setup_method(self): + """每个测试方法前执行""" + self.analyzer = PerformanceAnalyzer() + self.sample_data = { + 'requests': [ + {'response_time': 100, 'success': True}, + {'response_time': 200, 'success': True}, + {'response_time': 150, 'success': False} + ], + 'duration': 60, + 'users': 10 + } + + def test_analyze_performance_success(self): + """测试性能分析成功场景""" + # Given + expected_grade = 'B' + + # When + result = self.analyzer.analyze(self.sample_data) + + # Then + assert result['overall_grade'] == expected_grade + assert 'response_time' in result + assert 'throughput' in result + assert 'error_analysis' in result + + def test_analyze_performance_empty_data(self): + """测试空数据场景""" + # Given + empty_data = {'requests': [], 'duration': 0, 'users': 0} + + # When & Then + with pytest.raises(ValueError, match="测试数据不能为空"): + self.analyzer.analyze(empty_data) + + @patch('src.analysis.performance_analyzer.calculate_percentile') + def test_analyze_with_mock(self, mock_percentile): + """测试使用Mock""" + # Given + mock_percentile.return_value = 150.0 + + # When + result = self.analyzer.analyze(self.sample_data) + + # Then + mock_percentile.assert_called() + assert result['response_time']['p95'] == 150.0 + + @pytest.mark.parametrize("response_times,expected_grade", [ + ([100, 200, 150], 'B'), + ([50, 80, 60], 'A'), + ([500, 600, 700], 'D'), + ]) + def test_grading_algorithm(self, response_times, expected_grade): + """参数化测试评级算法""" + # Given + test_data = { + 'requests': [{'response_time': rt, 'success': True} for rt in response_times], + 'duration': 60, + 'users': 10 + } + + # When + result = self.analyzer.analyze(test_data) + + # Then + assert result['overall_grade'] == expected_grade +``` + +### 集成测试 + +```python +import pytest +from src.analysis.performance_analyzer import PerformanceAnalyzer +from src.data_manager.data_provider import DataProvider + +class TestAnalysisIntegration: + """分析模块集成测试""" + + @pytest.fixture + def analyzer(self): + """分析器fixture""" + return PerformanceAnalyzer() + + @pytest.fixture + def data_provider(self): + """数据提供者fixture""" + provider = DataProvider() + provider.load_test_data("tests/fixtures/sample_data.json") + return provider + + def test_end_to_end_analysis(self, analyzer, data_provider): + """端到端分析测试""" + # Given + test_data = data_provider.get_test_data("sample_test") + + # When + result = analyzer.comprehensive_analysis(test_data) + + # Then + assert result is not None + assert 'overall_grade' in result + assert result['overall_grade'] in ['A', 'B', 'C', 'D'] +``` + +## 📊 性能规范 + +### 性能要求 + +```python +import time +import functools + +def performance_monitor(max_time: float = 1.0): + """性能监控装饰器""" + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + start_time = time.time() + result = func(*args, **kwargs) + execution_time = time.time() - start_time + + if execution_time > max_time: + logger.warning( + f"函数 {func.__name__} 执行时间过长: {execution_time:.2f}s" + ) + + return result + return wrapper + return decorator + +# ✅ 使用性能监控 +@performance_monitor(max_time=0.5) +def analyze_large_dataset(data: List[Dict[str, Any]]) -> Dict[str, Any]: + """分析大数据集""" + # 实现分析逻辑 + pass + +# ✅ 内存优化 +def process_large_file(file_path: str) -> None: + """处理大文件""" + with open(file_path, 'r') as f: + for line in f: # 逐行处理,避免一次性加载 + process_line(line) + +# ✅ 缓存优化 +from functools import lru_cache + +class DataAnalyzer: + @lru_cache(maxsize=128) + def calculate_statistics(self, data_hash: str) -> Dict[str, float]: + """计算统计信息(带缓存)""" + # 耗时的统计计算 + pass +``` + +## 🔒 安全规范 + +```python +# ✅ 输入验证 +def validate_user_input(data: Dict[str, Any]) -> Dict[str, Any]: + """验证用户输入""" + from cerberus import Validator + + schema = { + 'username': {'type': 'string', 'minlength': 3, 'maxlength': 50}, + 'email': {'type': 'string', 'regex': r'^[^@]+@[^@]+\.[^@]+$'}, + 'age': {'type': 'integer', 'min': 0, 'max': 150} + } + + validator = Validator(schema) + if not validator.validate(data): + raise ValueError(f"输入验证失败: {validator.errors}") + + return validator.normalized(data) + +# ✅ 敏感信息处理 +import hashlib +import secrets + +def hash_password(password: str) -> str: + """安全的密码哈希""" + salt = secrets.token_hex(16) + password_hash = hashlib.pbkdf2_hmac('sha256', + password.encode('utf-8'), + salt.encode('utf-8'), + 100000) + return f"{salt}:{password_hash.hex()}" + +# ✅ 日志脱敏 +def sanitize_log_data(data: Dict[str, Any]) -> Dict[str, Any]: + """日志数据脱敏""" + sensitive_fields = ['password', 'token', 'secret', 'key'] + sanitized = data.copy() + + for field in sensitive_fields: + if field in sanitized: + sanitized[field] = "***" + + return sanitized +``` + +## 🔧 工具配置 + +### pre-commit配置 + +```yaml +# .pre-commit-config.yaml +repos: + - repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + language_version: python3.9 + + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + args: ["--profile", "black"] + + - repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + args: ["--max-line-length=88", "--extend-ignore=E203,W503"] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.0.1 + hooks: + - id: mypy + additional_dependencies: [types-requests] +``` + +### 代码质量检查 + +```bash +# 运行所有检查 +make lint + +# 单独运行各工具 +black --check src tests +isort --check-only src tests +flake8 src tests +pylint src +mypy src +``` + +遵循这些编码规范将确保代码库的一致性、可读性和可维护性,为团队协作和项目长期发展奠定坚实基础。 diff --git a/docs/development/contributing.md b/docs/development/contributing.md new file mode 100644 index 0000000..4f636fa --- /dev/null +++ b/docs/development/contributing.md @@ -0,0 +1,732 @@ +# 贡献指南 + +欢迎为Locust性能测试框架做出贡献!本指南将帮助您了解如何参与项目开发,提交代码和改进建议。 + +## 🎯 贡献方式 + +### 代码贡献 +- 修复Bug +- 添加新功能 +- 性能优化 +- 代码重构 + +### 文档贡献 +- 改进文档内容 +- 添加使用示例 +- 翻译文档 +- 修正错误 + +### 测试贡献 +- 编写单元测试 +- 添加集成测试 +- 性能测试 +- 兼容性测试 + +### 社区贡献 +- 回答问题 +- 分享经验 +- 提供反馈 +- 推广项目 + +## 🚀 开发环境搭建 + +### 1. 克隆项目 + +```bash +# 克隆主仓库 +git clone https://github.com/your-org/locust-framework.git +cd locust-framework + +# 添加上游仓库(如果是fork) +git remote add upstream https://github.com/original-org/locust-framework.git +``` + +### 2. 环境配置 + +```bash +# 创建虚拟环境 +python -m venv venv +source venv/bin/activate # Linux/Mac +# 或 +venv\Scripts\activate # Windows + +# 安装开发依赖 +pip install -r requirements-dev.txt + +# 安装pre-commit钩子 +pre-commit install +``` + +### 3. 开发依赖说明 + +```txt +# requirements-dev.txt +# 基础依赖 +locust>=2.0.0 +requests>=2.25.0 +gevent>=21.0.0 + +# 开发工具 +pytest>=6.0.0 +pytest-cov>=2.10.0 +pytest-mock>=3.5.0 +black>=21.0.0 +flake8>=3.8.0 +mypy>=0.800 +isort>=5.0.0 + +# 文档工具 +sphinx>=4.0.0 +sphinx-rtd-theme>=0.5.0 +myst-parser>=0.15.0 + +# 构建工具 +build>=0.7.0 +twine>=3.4.0 + +# 测试工具 +factory-boy>=3.2.0 +faker>=8.0.0 +responses>=0.13.0 +``` + +### 4. 开发配置 + +```python +# setup.cfg +[tool:pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = + --cov=src + --cov-report=html + --cov-report=term-missing + --strict-markers + --disable-warnings + +[coverage:run] +source = src +omit = + */tests/* + */venv/* + setup.py + +[coverage:report] +exclude_lines = + pragma: no cover + def __repr__ + raise AssertionError + raise NotImplementedError + +[flake8] +max-line-length = 88 +extend-ignore = E203, W503 +exclude = + .git, + __pycache__, + venv, + .venv, + build, + dist + +[mypy] +python_version = 3.8 +warn_return_any = True +warn_unused_configs = True +disallow_untyped_defs = True +``` + +## 📝 开发流程 + +### 1. 分支管理 + +```bash +# 创建功能分支 +git checkout -b feature/your-feature-name + +# 创建修复分支 +git checkout -b fix/issue-number-description + +# 创建文档分支 +git checkout -b docs/documentation-improvement +``` + +### 2. 代码规范 + +#### Python代码风格 + +```python +# 使用Black格式化代码 +black src/ tests/ + +# 使用isort整理导入 +isort src/ tests/ + +# 使用flake8检查代码质量 +flake8 src/ tests/ + +# 使用mypy进行类型检查 +mypy src/ +``` + +#### 代码示例 + +```python +# 好的代码示例 +from typing import Dict, List, Optional, Union +import logging + +logger = logging.getLogger(__name__) + +class LoadShapeManager: + """负载模式管理器 + + 管理和创建各种负载模式实例。 + + Attributes: + _shapes: 注册的负载模式字典 + _instances: 创建的实例缓存 + """ + + def __init__(self) -> None: + self._shapes: Dict[str, type] = {} + self._instances: Dict[str, object] = {} + + def register_shape(self, name: str, shape_class: type) -> None: + """注册负载模式 + + Args: + name: 负载模式名称 + shape_class: 负载模式类 + + Raises: + ValueError: 当名称已存在时 + """ + if name in self._shapes: + raise ValueError(f"Shape '{name}' already registered") + + self._shapes[name] = shape_class + logger.info(f"Registered load shape: {name}") + + def create_shape(self, name: str, **kwargs) -> Optional[object]: + """创建负载模式实例 + + Args: + name: 负载模式名称 + **kwargs: 初始化参数 + + Returns: + 负载模式实例,如果不存在则返回None + """ + if name not in self._shapes: + logger.error(f"Unknown load shape: {name}") + return None + + try: + instance = self._shapes[name](**kwargs) + self._instances[name] = instance + return instance + except Exception as e: + logger.error(f"Failed to create shape '{name}': {e}") + return None +``` + +### 3. 提交规范 + +#### 提交消息格式 + +``` +(): + + + +