main:添加核心文件并初始化项目
新增内容: - 创建基础项目结构。 - 添加 `.gitignore` 和 `.dockerignore` 文件。 - 编写 `pyproject.toml` 和依赖文件。 - 添加算法模块及示例算法。 - 实现核心功能模块(日志、错误处理、指标)。 - 添加开发和运行所需的相关脚本文件及文档。
This commit is contained in:
35
scripts/export_openapi.py
Normal file
35
scripts/export_openapi.py
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
"""导出 OpenAPI 规范到 JSON 文件"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加 src 到路径
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
||||
|
||||
from functional_scaffold.main import app
|
||||
|
||||
|
||||
def export_openapi():
|
||||
"""导出 OpenAPI 规范"""
|
||||
openapi_schema = app.openapi()
|
||||
|
||||
# 确保输出目录存在
|
||||
output_dir = Path(__file__).parent.parent / "docs" / "swagger"
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 写入文件
|
||||
output_file = output_dir / "openapi.json"
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(openapi_schema, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"OpenAPI schema exported to: {output_file}")
|
||||
print(f"Schema version: {openapi_schema.get('openapi')}")
|
||||
print(f"API title: {openapi_schema.get('info', {}).get('title')}")
|
||||
print(f"API version: {openapi_schema.get('info', {}).get('version')}")
|
||||
print(f"Endpoints: {len(openapi_schema.get('paths', {}))}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
export_openapi()
|
||||
22
scripts/generate_traffic.sh
Executable file
22
scripts/generate_traffic.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
# 生成测试流量脚本
|
||||
|
||||
echo "开始生成测试流量..."
|
||||
echo "按 Ctrl+C 停止"
|
||||
|
||||
count=0
|
||||
while true; do
|
||||
# 随机生成一个数字
|
||||
number=$((RANDOM % 1000 + 1))
|
||||
|
||||
# 发送请求
|
||||
curl -s -X POST http://localhost:8111/invoke \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"number\": $number}" > /dev/null
|
||||
|
||||
count=$((count + 1))
|
||||
echo "[$count] 已发送请求: number=$number"
|
||||
|
||||
# 随机延迟 0.5-2 秒
|
||||
sleep $(awk -v min=0.5 -v max=2 'BEGIN{srand(); print min+rand()*(max-min)}')
|
||||
done
|
||||
24
scripts/run_dev.sh
Executable file
24
scripts/run_dev.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# 开发环境启动脚本
|
||||
|
||||
set -e
|
||||
|
||||
echo "Starting FunctionalScaffold in development mode..."
|
||||
|
||||
# 检查虚拟环境
|
||||
if [ ! -d "venv" ]; then
|
||||
echo "Creating virtual environment..."
|
||||
python3 -m venv venv
|
||||
fi
|
||||
|
||||
# 激活虚拟环境
|
||||
source venv/bin/activate
|
||||
|
||||
# 安装依赖
|
||||
echo "Installing dependencies..."
|
||||
pip install -e ".[dev]"
|
||||
|
||||
# 启动服务
|
||||
echo "Starting server on http://localhost:8000"
|
||||
echo "API docs available at http://localhost:8000/docs"
|
||||
uvicorn src.functional_scaffold.main:app --reload --host 0.0.0.0 --port 8000
|
||||
28
scripts/run_tests.sh
Executable file
28
scripts/run_tests.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# 测试运行脚本
|
||||
|
||||
set -e
|
||||
|
||||
echo "Running tests for FunctionalScaffold..."
|
||||
|
||||
# 激活虚拟环境(如果存在)
|
||||
if [ -d "venv" ]; then
|
||||
source venv/bin/activate
|
||||
fi
|
||||
|
||||
# 运行代码检查
|
||||
echo "Running code quality checks..."
|
||||
echo "- Checking with ruff..."
|
||||
ruff check src/ tests/ || true
|
||||
|
||||
echo "- Checking formatting with black..."
|
||||
black --check src/ tests/ || true
|
||||
|
||||
# 运行测试
|
||||
echo ""
|
||||
echo "Running tests..."
|
||||
pytest tests/ -v --cov=src/functional_scaffold --cov-report=term --cov-report=html
|
||||
|
||||
echo ""
|
||||
echo "Tests completed!"
|
||||
echo "Coverage report available at: htmlcov/index.html"
|
||||
114
scripts/start_metrics.sh
Executable file
114
scripts/start_metrics.sh
Executable file
@@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
# 指标方案快速启动脚本
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo "=========================================="
|
||||
echo "FunctionalScaffold 指标方案启动脚本"
|
||||
echo "=========================================="
|
||||
|
||||
# 检查 docker-compose
|
||||
if ! command -v docker-compose &> /dev/null; then
|
||||
echo -e "${RED}错误: docker-compose 未安装${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 选择方案
|
||||
echo ""
|
||||
echo "请选择指标方案:"
|
||||
echo "1. Pushgateway(推荐,适合 Serverless)"
|
||||
echo "2. Redis + Exporter(适合高并发)"
|
||||
echo "3. 两者都启动(用于对比测试)"
|
||||
echo ""
|
||||
read -p "输入选项 (1/2/3): " choice
|
||||
|
||||
cd "$(dirname "$0")/../deployment"
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
echo -e "${GREEN}启动 Pushgateway 方案...${NC}"
|
||||
docker-compose up -d redis pushgateway prometheus grafana
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Pushgateway 方案已启动${NC}"
|
||||
echo ""
|
||||
echo "服务地址:"
|
||||
echo " - Pushgateway: http://localhost:9091"
|
||||
echo " - Prometheus: http://localhost:9090"
|
||||
echo " - Grafana: http://localhost:3000 (admin/admin)"
|
||||
echo ""
|
||||
echo "下一步:"
|
||||
echo " 1. 修改代码导入: from functional_scaffold.core.metrics_pushgateway import ..."
|
||||
echo " 2. 配置环境变量: PUSHGATEWAY_URL=localhost:9091"
|
||||
echo " 3. 启动应用: ./scripts/run_dev.sh"
|
||||
echo " 4. 运行测试: python scripts/test_metrics.py pushgateway"
|
||||
;;
|
||||
2)
|
||||
echo -e "${GREEN}启动 Redis 方案...${NC}"
|
||||
|
||||
# 检查 redis 依赖
|
||||
if ! python -c "import redis" 2>/dev/null; then
|
||||
echo -e "${YELLOW}警告: redis 库未安装${NC}"
|
||||
echo "正在安装 redis..."
|
||||
pip install redis
|
||||
fi
|
||||
|
||||
docker-compose up -d redis redis-exporter prometheus grafana
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Redis 方案已启动${NC}"
|
||||
echo ""
|
||||
echo "服务地址:"
|
||||
echo " - Redis: localhost:6379"
|
||||
echo " - Redis Exporter: http://localhost:8001/metrics"
|
||||
echo " - Prometheus: http://localhost:9090"
|
||||
echo " - Grafana: http://localhost:3000 (admin/admin)"
|
||||
echo ""
|
||||
echo "下一步:"
|
||||
echo " 1. 修改代码导入: from functional_scaffold.core.metrics_redis import ..."
|
||||
echo " 2. 配置环境变量: REDIS_HOST=localhost REDIS_PORT=6379"
|
||||
echo " 3. 启动应用: ./scripts/run_dev.sh"
|
||||
echo " 4. 运行测试: python scripts/test_metrics.py redis"
|
||||
;;
|
||||
3)
|
||||
echo -e "${GREEN}启动所有服务...${NC}"
|
||||
|
||||
# 检查 redis 依赖
|
||||
if ! python -c "import redis" 2>/dev/null; then
|
||||
echo -e "${YELLOW}警告: redis 库未安装${NC}"
|
||||
echo "正在安装 redis..."
|
||||
pip install redis
|
||||
fi
|
||||
|
||||
docker-compose up -d
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ 所有服务已启动${NC}"
|
||||
echo ""
|
||||
echo "服务地址:"
|
||||
echo " - 应用: http://localhost:8000"
|
||||
echo " - Pushgateway: http://localhost:9091"
|
||||
echo " - Redis: localhost:6379"
|
||||
echo " - Redis Exporter: http://localhost:8001/metrics"
|
||||
echo " - Prometheus: http://localhost:9090"
|
||||
echo " - Grafana: http://localhost:3000 (admin/admin)"
|
||||
echo ""
|
||||
echo "下一步:"
|
||||
echo " 1. 查看文档: cat docs/metrics-guide.md"
|
||||
echo " 2. 运行测试: python scripts/test_metrics.py"
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}无效的选项${NC}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "查看日志: docker-compose logs -f"
|
||||
echo "停止服务: docker-compose down"
|
||||
echo "查看文档: cat ../docs/metrics-guide.md"
|
||||
echo "=========================================="
|
||||
262
scripts/test_metrics.py
Executable file
262
scripts/test_metrics.py
Executable file
@@ -0,0 +1,262 @@
|
||||
#!/usr/bin/env python3
|
||||
"""指标方案测试脚本"""
|
||||
|
||||
import requests
|
||||
import time
|
||||
import sys
|
||||
from typing import Literal
|
||||
|
||||
MetricsBackend = Literal["pushgateway", "redis", "memory"]
|
||||
|
||||
|
||||
def test_pushgateway():
|
||||
"""测试 Pushgateway 方案"""
|
||||
print("\n=== 测试 Pushgateway 方案 ===\n")
|
||||
|
||||
# 1. 检查 Pushgateway 是否运行
|
||||
try:
|
||||
response = requests.get("http://localhost:9091/metrics", timeout=2)
|
||||
print(f"✓ Pushgateway 运行正常 (状态码: {response.status_code})")
|
||||
except Exception as e:
|
||||
print(f"✗ Pushgateway 未运行: {e}")
|
||||
return False
|
||||
|
||||
# 2. 发送测试请求到应用
|
||||
print("\n发送测试请求...")
|
||||
for i in range(5):
|
||||
try:
|
||||
response = requests.post(
|
||||
"http://localhost:8000/invoke",
|
||||
json={"number": 17},
|
||||
timeout=5,
|
||||
)
|
||||
print(f" 请求 {i+1}: {response.status_code}")
|
||||
time.sleep(0.5)
|
||||
except Exception as e:
|
||||
print(f" 请求 {i+1} 失败: {e}")
|
||||
|
||||
# 3. 等待指标推送
|
||||
print("\n等待指标推送...")
|
||||
time.sleep(2)
|
||||
|
||||
# 4. 检查 Pushgateway 中的指标
|
||||
try:
|
||||
response = requests.get("http://localhost:9091/metrics", timeout=2)
|
||||
metrics = response.text
|
||||
|
||||
# 查找关键指标
|
||||
if "http_requests_total" in metrics:
|
||||
print("✓ 找到 http_requests_total 指标")
|
||||
# 提取指标值
|
||||
for line in metrics.split("\n"):
|
||||
if "http_requests_total" in line and not line.startswith("#"):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print("✗ 未找到 http_requests_total 指标")
|
||||
|
||||
if "algorithm_executions_total" in metrics:
|
||||
print("✓ 找到 algorithm_executions_total 指标")
|
||||
for line in metrics.split("\n"):
|
||||
if "algorithm_executions_total" in line and not line.startswith("#"):
|
||||
print(f" {line}")
|
||||
else:
|
||||
print("✗ 未找到 algorithm_executions_total 指标")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 获取指标失败: {e}")
|
||||
return False
|
||||
|
||||
# 5. 检查 Prometheus 是否能抓取
|
||||
print("\n检查 Prometheus...")
|
||||
try:
|
||||
response = requests.get(
|
||||
"http://localhost:9090/api/v1/query",
|
||||
params={"query": "http_requests_total"},
|
||||
timeout=5,
|
||||
)
|
||||
data = response.json()
|
||||
if data["status"] == "success" and data["data"]["result"]:
|
||||
print(f"✓ Prometheus 成功抓取指标,找到 {len(data['data']['result'])} 条记录")
|
||||
for result in data["data"]["result"][:3]:
|
||||
print(f" {result['metric']} = {result['value'][1]}")
|
||||
else:
|
||||
print("✗ Prometheus 未找到指标")
|
||||
except Exception as e:
|
||||
print(f"✗ Prometheus 查询失败: {e}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_redis():
|
||||
"""测试 Redis 方案"""
|
||||
print("\n=== 测试 Redis 方案 ===\n")
|
||||
|
||||
# 1. 检查 Redis 是否运行
|
||||
try:
|
||||
import redis
|
||||
|
||||
client = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)
|
||||
client.ping()
|
||||
print("✓ Redis 运行正常")
|
||||
except ImportError:
|
||||
print("✗ Redis 库未安装,请运行: pip install redis")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"✗ Redis 未运行: {e}")
|
||||
return False
|
||||
|
||||
# 2. 清空测试数据
|
||||
print("\n清空旧数据...")
|
||||
try:
|
||||
keys = client.keys("metrics:*")
|
||||
if keys:
|
||||
client.delete(*keys)
|
||||
print(f" 删除了 {len(keys)} 个键")
|
||||
except Exception as e:
|
||||
print(f" 清空失败: {e}")
|
||||
|
||||
# 3. 发送测试请求
|
||||
print("\n发送测试请求...")
|
||||
for i in range(5):
|
||||
try:
|
||||
response = requests.post(
|
||||
"http://localhost:8000/invoke",
|
||||
json={"number": 17},
|
||||
timeout=5,
|
||||
)
|
||||
print(f" 请求 {i+1}: {response.status_code}")
|
||||
time.sleep(0.5)
|
||||
except Exception as e:
|
||||
print(f" 请求 {i+1} 失败: {e}")
|
||||
|
||||
# 4. 检查 Redis 中的指标
|
||||
print("\n检查 Redis 指标...")
|
||||
try:
|
||||
# 检查计数器
|
||||
counter_data = client.hgetall("metrics:request_counter")
|
||||
if counter_data:
|
||||
print(f"✓ 找到 {len(counter_data)} 个请求计数器指标")
|
||||
for key, value in list(counter_data.items())[:5]:
|
||||
if not key.endswith(":timestamp"):
|
||||
print(f" {key} = {value}")
|
||||
else:
|
||||
print("✗ 未找到请求计数器指标")
|
||||
|
||||
# 检查算法计数器
|
||||
algo_data = client.hgetall("metrics:algorithm_counter")
|
||||
if algo_data:
|
||||
print(f"✓ 找到 {len(algo_data)} 个算法计数器指标")
|
||||
for key, value in list(algo_data.items())[:5]:
|
||||
if not key.endswith(":timestamp"):
|
||||
print(f" {key} = {value}")
|
||||
else:
|
||||
print("✗ 未找到算法计数器指标")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 检查 Redis 失败: {e}")
|
||||
return False
|
||||
|
||||
# 5. 检查 Redis Exporter
|
||||
print("\n检查 Redis Exporter...")
|
||||
try:
|
||||
response = requests.get("http://localhost:8001/metrics", timeout=2)
|
||||
metrics = response.text
|
||||
|
||||
if "http_requests_total" in metrics:
|
||||
print("✓ Exporter 成功导出 http_requests_total")
|
||||
for line in metrics.split("\n"):
|
||||
if "http_requests_total" in line and not line.startswith("#"):
|
||||
print(f" {line}")
|
||||
break
|
||||
else:
|
||||
print("✗ Exporter 未导出 http_requests_total")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Redis Exporter 未运行: {e}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_memory():
|
||||
"""测试原有的内存方案"""
|
||||
print("\n=== 测试内存方案(原有方案)===\n")
|
||||
|
||||
# 发送测试请求
|
||||
print("发送测试请求...")
|
||||
for i in range(5):
|
||||
try:
|
||||
response = requests.post(
|
||||
"http://localhost:8000/invoke",
|
||||
json={"number": 17},
|
||||
timeout=5,
|
||||
)
|
||||
print(f" 请求 {i+1}: {response.status_code}")
|
||||
time.sleep(0.5)
|
||||
except Exception as e:
|
||||
print(f" 请求 {i+1} 失败: {e}")
|
||||
|
||||
# 检查应用的 /metrics 端点
|
||||
print("\n检查应用 /metrics 端点...")
|
||||
try:
|
||||
response = requests.get("http://localhost:8000/metrics", timeout=2)
|
||||
metrics = response.text
|
||||
|
||||
if "http_requests_total" in metrics:
|
||||
print("✓ 找到 http_requests_total 指标")
|
||||
for line in metrics.split("\n"):
|
||||
if "http_requests_total" in line and not line.startswith("#"):
|
||||
print(f" {line}")
|
||||
break
|
||||
else:
|
||||
print("✗ 未找到指标")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 获取指标失败: {e}")
|
||||
return False
|
||||
|
||||
print("\n⚠️ 注意:内存方案在多实例部署时,每个实例的指标是独立的")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("FunctionalScaffold 指标方案测试")
|
||||
print("=" * 60)
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
backend = sys.argv[1]
|
||||
else:
|
||||
print("\n请选择要测试的方案:")
|
||||
print("1. Pushgateway(推荐)")
|
||||
print("2. Redis + Exporter")
|
||||
print("3. Memory(原有方案)")
|
||||
choice = input("\n输入选项 (1/2/3): ").strip()
|
||||
|
||||
backend_map = {"1": "pushgateway", "2": "redis", "3": "memory"}
|
||||
backend = backend_map.get(choice, "pushgateway")
|
||||
|
||||
print(f"\n选择的方案: {backend}")
|
||||
|
||||
# 运行测试
|
||||
if backend == "pushgateway":
|
||||
success = test_pushgateway()
|
||||
elif backend == "redis":
|
||||
success = test_redis()
|
||||
elif backend == "memory":
|
||||
success = test_memory()
|
||||
else:
|
||||
print(f"未知的方案: {backend}")
|
||||
sys.exit(1)
|
||||
|
||||
# 输出结果
|
||||
print("\n" + "=" * 60)
|
||||
if success:
|
||||
print("✓ 测试通过")
|
||||
else:
|
||||
print("✗ 测试失败")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user