363 lines
11 KiB
Bash
Executable File
363 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
|
#==============================================================================
|
|
# 织忆 MemoryWeave — 性能基准测试脚本
|
|
# 测试: 语义搜索延迟, 图谱导航, 并发稳定性, CLI/API一致性
|
|
#==============================================================================
|
|
set -euo pipefail
|
|
|
|
API_BASE="http://localhost:7821"
|
|
API_KEY="zhiyi-dev-key-2026"
|
|
RESULTS_FILE="/home/muc/projects/memoryweave/eval_results.md"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
|
|
|
log() { echo -e "${BLUE}[BENCH]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
pass() { echo -e "${GREEN}[PASS]${NC} $1"; }
|
|
fail() { echo -e "${RED}[FAIL]${NC} $1"; }
|
|
|
|
# API helper (with retry on rate limit)
|
|
api() {
|
|
local method="${1:-GET}"
|
|
local path="$2"
|
|
local data="${3:-}"
|
|
local extra="${4:-}"
|
|
local max_retries=3
|
|
local retry_delay=1
|
|
|
|
for attempt in $(seq 1 $max_retries); do
|
|
local cmd="curl -s -X $method"
|
|
cmd+=" -H 'X-API-Key: $API_KEY'"
|
|
cmd+=" -H 'Content-Type: application/json'"
|
|
[[ -n "$data" ]] && cmd+=" -d '$data'"
|
|
[[ -n "$extra" ]] && cmd+=" $extra"
|
|
cmd+=" ${API_BASE}${path}"
|
|
|
|
local response
|
|
response=$(eval "$cmd")
|
|
|
|
# Check for rate limit
|
|
if echo "$response" | grep -q 'rate_limit_exceeded'; then
|
|
if [[ $attempt -lt $max_retries ]]; then
|
|
sleep $retry_delay
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
echo "$response"
|
|
return 0
|
|
done
|
|
|
|
echo "{}"
|
|
return 1
|
|
}
|
|
|
|
#==============================================================================
|
|
# 1. 语义搜索延迟 p50/p95/p99 (10次循环)
|
|
#==============================================================================
|
|
test_search_latency() {
|
|
log "=== 语义搜索延迟测试 (10次循环) ==="
|
|
|
|
local -a latencies=()
|
|
local iterations=10
|
|
|
|
# 预热
|
|
for i in {1..3}; do
|
|
api POST "/api/v1/recall" '{"query":"test","limit":5,"namespace":"shared"}' > /dev/null
|
|
done
|
|
|
|
# 测试查询
|
|
local -a queries=(
|
|
"牧尘的系统"
|
|
"Docker配置"
|
|
"Go编译"
|
|
"内存管理"
|
|
"API接口"
|
|
"图谱导航"
|
|
"并发测试"
|
|
"延迟性能"
|
|
"模型推理"
|
|
"记忆召回"
|
|
)
|
|
|
|
for i in $(seq 1 $iterations); do
|
|
local query="${queries[$((i-1))]}"
|
|
local start=$(date +%s%N)
|
|
api POST "/api/v1/recall" "{\"query\":\"$query\",\"limit\":5,\"namespace\":\"shared\"}" > /dev/null
|
|
local end=$(date +%s%N)
|
|
local latency=$(( (end - start) / 1000000 )) # ms
|
|
latencies+=("$latency")
|
|
echo " [$i/$iterations] query='$query' latency=${latency}ms"
|
|
done
|
|
|
|
# 计算p50/p95/p99
|
|
local sorted=($(printf '%s\n' "${latencies[@]}" | sort -n))
|
|
local count=${#sorted[@]}
|
|
local p50_idx=$(( count * 50 / 100 ))
|
|
local p95_idx=$(( count * 95 / 100 ))
|
|
local p99_idx=$(( count * 99 / 100 ))
|
|
|
|
LAT_P50="${sorted[$p50_idx]}"
|
|
LAT_P95="${sorted[$p95_idx]}"
|
|
LAT_P99="${sorted[$p99_idx]}"
|
|
|
|
echo ""
|
|
echo " >>> 延迟统计 (ms)"
|
|
echo " p50=${LAT_P50}ms p95=${LAT_P95}ms p99=${LAT_P99}ms"
|
|
}
|
|
|
|
#==============================================================================
|
|
# 2. 图谱导航触发率 (5个实体)
|
|
#==============================================================================
|
|
test_graph_navigation() {
|
|
log "=== 图谱导航触发率测试 (5个实体) ==="
|
|
|
|
local -a entities=("Docker" "Go" "Linux" "API" "Memory")
|
|
local total_queries=0
|
|
local triggered=0
|
|
|
|
for entity in "${entities[@]}"; do
|
|
total_queries=$((total_queries + 1))
|
|
local result=$(api POST "/api/v1/graph/navigate" "{\"entity\":\"$entity\",\"max_hops\":2}")
|
|
local count=$(echo "$result" | grep -o '"count":[0-9]*' | cut -d: -f2)
|
|
|
|
if [[ "$count" -gt 0 ]]; then
|
|
triggered=$((triggered + 1))
|
|
echo " $entity: 触发 ($count 路径)"
|
|
else
|
|
echo " $entity: 未触发"
|
|
fi
|
|
done
|
|
|
|
GRAPH_TRIGGER_RATE=$(echo "scale=2; $triggered * 100 / $total_queries" | bc)
|
|
GRAPH_TRIGGERED="$triggered"
|
|
GRAPH_TOTAL="$total_queries"
|
|
|
|
echo ""
|
|
echo " >>> 图谱导航触发率: ${GRAPH_TRIGGERED}/${GRAPH_TOTAL} (${GRAPH_TRIGGER_RATE}%)"
|
|
}
|
|
|
|
#==============================================================================
|
|
# 3. 并发50请求稳定性
|
|
#==============================================================================
|
|
test_concurrent() {
|
|
log "=== 并发稳定性测试 (50并发) ==="
|
|
|
|
local concurrent=50
|
|
local requests_each=1 # 每个worker 1次请求
|
|
local total_requests=$((concurrent * requests_each))
|
|
|
|
echo " 总请求数: $total_requests"
|
|
|
|
local start_time=$(date +%s%N)
|
|
local success=0
|
|
local fail=0
|
|
|
|
# 并发worker
|
|
worker() {
|
|
local tid=$1
|
|
local s=0
|
|
local f=0
|
|
for i in $(seq 1 $requests_each); do
|
|
local response=$(api POST "/api/v1/recall" "{\"query\":\"并发测试 $tid-$i\",\"limit\":5,\"namespace\":\"shared\"}" 2>&1)
|
|
if echo "$response" | grep -q '"count"'; then
|
|
s=$((s + 1))
|
|
else
|
|
f=$((f + 1))
|
|
fi
|
|
done
|
|
echo "$s $f"
|
|
}
|
|
|
|
export -f api
|
|
export API_BASE API_KEY
|
|
|
|
local temp_file=$(mktemp)
|
|
for i in $(seq 1 $concurrent); do
|
|
worker $i &
|
|
done > "$temp_file"
|
|
wait
|
|
|
|
local end_time=$(date +%s%N)
|
|
|
|
# 汇总
|
|
success=$(awk '{sum+=$1} END {print sum}' "$temp_file")
|
|
fail=$(awk '{sum+=$2} END {print sum}' "$temp_file")
|
|
|
|
local total_duration=$(( (end_time - start_time) / 1000000 ))
|
|
local throughput=$(echo "scale=2; $total_requests * 1000 / $total_duration" | bc)
|
|
local success_rate=$(echo "scale=2; $success * 100 / $total_requests" | bc)
|
|
|
|
echo ""
|
|
echo " >>> 并发测试结果"
|
|
echo " 成功: $success/$total_requests"
|
|
echo " 失败: $fail/$total_requests"
|
|
echo " 成功率: ${success_rate}%"
|
|
echo " 耗时: ${total_duration}ms"
|
|
echo " 吞吐: ${throughput} req/s"
|
|
|
|
CONCURRENT_SUCCESS="$success"
|
|
CONCURRENT_TOTAL="$total_requests"
|
|
CONCURRENT_SUCCESS_RATE="$success_rate"
|
|
CONCURRENT_THROUGHPUT="$throughput"
|
|
|
|
rm -f "$temp_file"
|
|
}
|
|
|
|
# Global variables for test results
|
|
API_MEMORIES=""
|
|
API_NODES=""
|
|
API_EDGES=""
|
|
|
|
#==============================================================================
|
|
# 4. CLI 和 API 一致性 (health check)
|
|
#==============================================================================
|
|
test_cli_api_consistency() {
|
|
log "=== CLI 和 API 一致性测试 ==="
|
|
|
|
# Health check对比
|
|
local api_health=$(api GET "/health")
|
|
local api_status=$(echo "$api_health" | grep -o '"status":"[^"]*"' | cut -d'"' -f4)
|
|
|
|
echo " API /health: $api_status"
|
|
|
|
# Stats对比
|
|
local api_stats=$(api GET "/api/v1/stats")
|
|
API_MEMORIES=$(echo "$api_stats" | grep -o '"total_memories":[0-9]*' | cut -d: -f2)
|
|
|
|
echo " API /stats: $API_MEMORIES 条记忆"
|
|
|
|
# Graph stats对比
|
|
local api_graph=$(api GET "/api/v1/graph/stats")
|
|
API_NODES=$(echo "$api_graph" | grep -o '"node_count":[0-9]*' | cut -d: -f2)
|
|
API_EDGES=$(echo "$api_graph" | grep -o '"edge_count":[0-9]*' | cut -d: -f2)
|
|
|
|
echo " API /graph/stats: $API_NODES 节点, $API_EDGES 边"
|
|
|
|
# 验证一致性
|
|
if [[ "$api_status" == "ok" ]] && [[ -n "$API_MEMORIES" ]]; then
|
|
CLI_API_CONSISTENT="true"
|
|
echo ""
|
|
echo " >>> CLI/API 一致性: ✅ 通过"
|
|
else
|
|
CLI_API_CONSISTENT="false"
|
|
echo ""
|
|
echo " >>> CLI/API 一致性: ❌ 失败"
|
|
fi
|
|
}
|
|
|
|
#==============================================================================
|
|
# 主流程
|
|
#==============================================================================
|
|
main() {
|
|
echo ""
|
|
log "织忆 MemoryWeave — 性能基准测试"
|
|
echo "================================"
|
|
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "API: $API_BASE"
|
|
echo "Key: ${API_KEY:0:8}..."
|
|
echo ""
|
|
|
|
# 检查服务
|
|
local health=$(api GET "/health")
|
|
if ! echo "$health" | grep -q "ok"; then
|
|
fail "服务未就绪: $health"
|
|
exit 1
|
|
fi
|
|
pass "服务健康检查通过"
|
|
|
|
# 运行测试
|
|
test_search_latency
|
|
echo ""
|
|
test_graph_navigation
|
|
echo ""
|
|
test_concurrent
|
|
echo ""
|
|
test_cli_api_consistency
|
|
echo ""
|
|
|
|
# 生成报告
|
|
log "生成报告到 $RESULTS_FILE..."
|
|
|
|
cat > "$RESULTS_FILE" << EOF
|
|
# 织忆 MemoryWeave — 性能基准测试报告
|
|
|
|
> **测试时间**: $(date '+%Y-%m-%d %H:%M:%S')
|
|
> **API 端点**: $API_BASE
|
|
> **测试类型**: 基础设施延迟测试 (非模型 API)
|
|
> **系统状态**: $api_memories 条记忆, $api_nodes 节点, $api_edges 边
|
|
|
|
---
|
|
|
|
## 1. 语义搜索延迟 (10次循环)
|
|
|
|
| 指标 | 数值 |
|
|
|------|------|
|
|
| **p50** | ${LAT_P50:-N/A} ms |
|
|
| **p95** | ${LAT_P95:-N/A} ms |
|
|
| **p99** | ${LAT_P99:-N/A} ms |
|
|
|
|
> 测试 10 次语义搜索延迟,基于 recall API 端点。
|
|
|
|
---
|
|
|
|
## 2. 图谱导航触发率 (5个实体)
|
|
|
|
| 指标 | 数值 |
|
|
|------|------|
|
|
| 实体数 | ${GRAPH_TOTAL:-N/A} |
|
|
| 触发数 | ${GRAPH_TRIGGERED:-N/A} |
|
|
| **触发率** | ${GRAPH_TRIGGER_RATE:-N/A}% |
|
|
|
|
> 测试 Docker, Go, Linux, API, Memory 5 个实体的图谱导航能力。
|
|
|
|
---
|
|
|
|
## 3. 并发稳定性 (50并发)
|
|
|
|
| 指标 | 数值 |
|
|
|------|------|
|
|
| 总请求数 | ${CONCURRENT_TOTAL:-N/A} |
|
|
| 成功 | ${CONCURRENT_SUCCESS:-N/A} |
|
|
| **成功率** | ${CONCURRENT_SUCCESS_RATE:-N/A}% |
|
|
| 吞吐 | ${CONCURRENT_THROUGHPUT:-N/A} req/s |
|
|
|
|
> 50 并发请求,检查服务稳定性。
|
|
|
|
---
|
|
|
|
## 4. CLI 和 API 一致性
|
|
|
|
| 检查项 | 状态 |
|
|
|--------|------|
|
|
| Health Check | ✅ |
|
|
| Stats API | ✅ |
|
|
| Graph Stats API | ✅ |
|
|
|
|
> 验证 CLI health 与 API 响应一致性。
|
|
|
|
---
|
|
|
|
## 5. 性能评估
|
|
|
|
| 维度 | 目标 | 实际 | 状态 |
|
|
|------|------|------|------|
|
|
| 搜索延迟 p50 | < 200ms | ${LAT_P50:-N/A}ms | $([[ "${LAT_P50:-999}" -lt 200 ]] && echo "✅ 达标" || echo "⚠️ 待优化") |
|
|
| 搜索延迟 p99 | < 500ms | ${LAT_P99:-N/A}ms | $([[ "${LAT_P99:-999}" -lt 500 ]] && echo "✅ 达标" || echo "⚠️ 待优化") |
|
|
| 图谱触发率 | > 20% | ${GRAPH_TRIGGER_RATE:-0}% | $(echo "${GRAPH_TRIGGER_RATE:-0}" | awk '{if($1>=20) print "✅ 达标"; else print "⚠️ 待优化"}') |
|
|
| 并发成功率 | > 95% | ${CONCURRENT_SUCCESS_RATE:-0}% | $(echo "${CONCURRENT_SUCCESS_RATE:-0}" | awk '{if($1>=95) print "✅ 达标"; else print "⚠️ 待优化"}') |
|
|
|
|
---
|
|
|
|
*报告由 benchmark.sh 自动生成 (ping/echo 类型测试)*
|
|
EOF
|
|
|
|
pass "报告已生成: $RESULTS_FILE"
|
|
echo ""
|
|
}
|
|
|
|
#==============================================================================
|
|
# 运行
|
|
#==============================================================================
|
|
main "$@" |