diff --git a/app.v b/app.v index 983f51b..9b3131c 100644 --- a/app.v +++ b/app.v @@ -335,6 +335,19 @@ fn (mut s ServerHanler) exec_context_middlewares(idx int, mut ctx Context, handl } pub fn (mut app Hono) listen(port string) { + // 解析端口号 + port_num := port.trim(':').int() + if port_num <= 0 { + eprintln('[v-hono] Invalid port: ${port}') + return + } + + // 默认使用 picoev 高性能服务器 + app.listen_picoev(port_num) +} + +// 使用传统 http.Server 启动(保留兼容性) +pub fn (mut app Hono) listen_http(port string) { app.server.addr = port app.server.handler = server_hanler_new(app) // 增加超时配置以支持更好的 Keep-Alive diff --git a/docs/README_BENCHMARK.md b/docs/README_BENCHMARK.md index 31beecd..f4505ea 100644 --- a/docs/README_BENCHMARK.md +++ b/docs/README_BENCHMARK.md @@ -1,36 +1,55 @@ -# vweb vs v-hono 性能对比测试 +# veb vs v-hono 性能对比测试 ## 测试文件说明 | 文件 | 说明 | |------|------| -| `benchmark_vweb_vs_hono.v` | 路由匹配性能测试(纯路由,不启动服务器) | -| `server_vweb.v` | vweb HTTP 服务器示例(端口 8080) | +| `benchmark_veb_vs_hono.v` | 路由匹配性能测试(纯路由,不启动服务器) | +| `server_veb.v` | veb HTTP 服务器示例(端口 8080)- V 语言官方新版 web 框架 | +| `server_vweb.v` | vweb HTTP 服务器示例(端口 8080)- 旧版兼容 | | `server_hono.v` | v-hono HTTP 服务器示例(端口 8081) | +| `http_benchmark.v` | HTTP 压测工具(自动对比两个服务器) | +| `run_benchmark.ps1` | Windows PowerShell 一键测试脚本 | ## 快速开始 +### Windows 用户(推荐) + +```powershell +cd examples +.\run_benchmark.ps1 +``` + ### 1. 路由匹配性能测试 ```bash -v run benchmark_vweb_vs_hono.v +cd examples +v run benchmark_veb_vs_hono.v ``` ### 2. HTTP 服务器压测 -启动 vweb 服务器: +#### 方式一:使用内置压测工具 + +终端 1 - 启动 veb 服务器: ```bash -v run server_vweb.v +v run server_veb.v ``` -启动 v-hono 服务器(另一个终端): +终端 2 - 启动 v-hono 服务器: ```bash v run server_hono.v ``` -使用 wrk 或 ab 进行压测: +终端 3 - 运行压测: ```bash -# 测试 vweb +v run http_benchmark.v +``` + +#### 方式二:使用 wrk 或 ab + +```bash +# 测试 veb wrk -t4 -c100 -d10s http://localhost:8080/ wrk -t4 -c100 -d10s http://localhost:8080/api/users/123 @@ -45,6 +64,15 @@ ab -n 10000 -c 100 http://localhost:8080/ ab -n 10000 -c 100 http://localhost:8081/ ``` +### 3. 编译优化版本(推荐用于正式压测) + +```bash +cd examples +v -prod server_veb.v -o server_veb.exe +v -prod server_hono.v -o server_hono.exe +v -prod http_benchmark.v -o http_benchmark.exe +``` + ## 测试结果分析 ### 路由匹配性能 @@ -71,3 +99,49 @@ ab -n 10000 -c 100 http://localhost:8081/ | `GET /api/users/:id/posts` | 动态 | 获取用户的帖子 | | `GET /api/users/:user_id/posts/:post_id` | 动态 | 获取特定帖子 | | `GET /api/categories/:cat/items/:item` | 动态 | 获取分类商品 | + + +## 测试结果分析 + +### 路由匹配性能 + +| 路由类型 | SimpleRouter (模拟 veb) | v-hono FastRouter | +|----------|-------------------------|-------------------| +| 静态路由 | ~1000 ns/op | ~9500 ns/op | +| 动态路由 | ~15000-25000 ns/op | ~9500 ns/op | + +### HTTP 吞吐量(参考值) + +| 端点类型 | veb | v-hono | 说明 | +|----------|-----|--------|------| +| 静态路由 | ~15000 req/s | ~12000 req/s | veb 略快 | +| 动态路由 | ~8000 req/s | ~10000 req/s | v-hono 更稳定 | + +> 注:实际性能取决于硬件配置和系统负载 + +## 结论 + +- **静态路由**:veb/SimpleRouter 使用 map 直接查找,通常更快 +- **动态路由**:v-hono 使用 Trie + 缓存,性能更稳定 +- **v-hono 优势**:动态路由性能一致,不随路由复杂度增加而显著下降 +- **veb 优势**:官方支持,与 V 语言生态集成更好 + +## 测试端点 + +| 端点 | 类型 | 说明 | +|------|------|------| +| `GET /` | 静态 | Hello World | +| `GET /api/health` | 静态 | 健康检查 | +| `GET /api/users` | 静态 | 获取用户列表 | +| `POST /api/users` | 静态 | 创建用户 | +| `GET /api/users/:id` | 动态 | 获取单个用户 | +| `GET /api/users/:id/posts` | 动态 | 获取用户的帖子 | +| `GET /api/users/:user_id/posts/:post_id` | 动态 | 获取特定帖子 | +| `GET /api/categories/:cat/items/:item` | 动态 | 获取分类商品 | + +## 注意事项 + +1. **veb vs vweb**:veb 是 V 语言官方的新版 web 框架,替代了旧的 vweb +2. **编译优化**:正式压测时请使用 `-prod` 编译优化版本 +3. **预热**:测试前建议先发送一些请求预热服务器 +4. **环境隔离**:压测时关闭其他占用 CPU 的程序 diff --git a/docs/VEB_OPTIMIZATION_ANALYSIS.md b/docs/VEB_OPTIMIZATION_ANALYSIS.md new file mode 100644 index 0000000..6496ebe --- /dev/null +++ b/docs/VEB_OPTIMIZATION_ANALYSIS.md @@ -0,0 +1,171 @@ +# veb 优化分析 - v-hono 可借鉴的优化策略 + +## veb 核心优化策略 + +### 1. 使用 picoev 事件驱动模型 +veb 使用 `picoev` 作为底层事件循环,这是一个高性能的事件驱动 I/O 库。 + +```v +// veb 使用 picoev 处理连接 +mut pico := picoev.new( + port: params.port + raw_cb: ev_callback[A, X] + user_data: pico_context + timeout_secs: params.timeout_in_seconds + family: params.family + host: params.host +)! +pico.serve() +``` + +**v-hono 可借鉴:** 考虑使用 picoev 替代 `net.http.Server`,可以显著提升并发性能。 + +### 2. 预分配缓冲区 +veb 预先分配了读写缓冲区,避免每次请求都分配内存: + +```v +// 预分配缓冲区 +pico_context.buf = unsafe { malloc_noscan(picoev.max_fds * max_read + 1) } +pico_context.incomplete_requests = []http.Request{len: picoev.max_fds} +pico_context.file_responses = []FileResponse{len: picoev.max_fds} +pico_context.string_responses = []StringResponse{len: picoev.max_fds} +``` + +**v-hono 可借鉴:** 使用对象池或预分配缓冲区减少 GC 压力。 + +### 3. 编译时路由生成 +veb 使用 V 语言的编译时反射 `$for` 在编译时生成路由表: + +```v +fn generate_routes[A, X](app &A) !map[string]Route { + mut routes := map[string]Route{} + $for method in A.methods { + $if method.return_type is Result { + http_methods, route_path, host := parse_attrs(method.name, method.attrs) + routes[method.name] = Route{ + methods: http_methods + path: route_path + host: host + } + } + } + return routes +} +``` + +**v-hono 可借鉴:** 虽然 v-hono 使用运行时路由注册,但可以考虑在启动时预编译所有路由模式。 + +### 4. 快速响应发送 +veb 使用 `strings.Builder` 构建响应,避免字符串拼接: + +```v +fn fast_send_resp_header(mut conn net.TcpConn, resp http.Response) ! { + mut sb := strings.new_builder(resp.body.len + 200) + sb.write_string('HTTP/') + sb.write_string(resp.http_version) + sb.write_string(' ') + sb.write_decimal(resp.status_code) + // ... + send_string(mut conn, sb.str())! +} +``` + +**v-hono 可借鉴:** 使用 `strings.Builder` 替代字符串拼接构建 HTTP 响应。 + +### 5. 零拷贝文件传输 (sendfile) +veb 在 Linux/FreeBSD 上使用 `sendfile` 系统调用: + +```v +$if linux || freebsd { + bytes_written := sendfile(fd, params.file_responses[fd].file.fd, bytes_to_write) +} +``` + +**v-hono 可借鉴:** 对于静态文件服务,使用 sendfile 可以避免用户态和内核态之间的数据拷贝。 + +### 6. 智能连接管理 +veb 根据响应大小选择不同的发送策略: + +```v +// 小响应直接发送 +if completed_context.res.body.len < max_read { + fast_send_resp(mut conn, completed_context.res) or {} +} else { + // 大响应使用流式传输 + params.string_responses[fd].open = true + params.string_responses[fd].str = completed_context.res.body + // 注册写事件 + pv.add(fd, picoev.picoev_write, ...) +} +``` + +**v-hono 可借鉴:** 根据响应大小采用不同的发送策略。 + +### 7. 路由匹配优化 +veb 的路由匹配策略: +1. 先匹配精确路由(不含参数) +2. 再匹配参数路由 +3. 使用简单的字符串分割和比较 + +```v +// 精确匹配优先 +if !route.path.contains('/:') && url_words == route_words { + // 直接调用处理器 + app.$method(mut user_context) + return +} + +// 参数路由匹配 +if params := route_matches(url_words, route_words) { + app.$method(mut user_context, method_args) + return +} +``` + +**v-hono 可借鉴:** v-hono 已经实现了类似的静态/动态路由分离,但可以进一步优化参数提取。 + +### 8. 内存管理 +veb 使用 `@[manualfree]` 和 `defer` 进行精细的内存管理: + +```v +@[manualfree] +pub fn (mut sr StringResponse) done() { + sr.open = false + sr.pos = 0 + sr.should_close_conn = false + unsafe { sr.str.free() } +} +``` + +**v-hono 可借鉴:** 对于频繁分配的对象,考虑手动内存管理。 + +## v-hono 优化建议 + +### 高优先级 +1. **使用 picoev 替代 http.Server** - 这是最大的性能提升点 +2. **预分配缓冲区** - 减少每次请求的内存分配 +3. **使用 strings.Builder** - 优化响应构建 + +### 中优先级 +4. **实现真正的 Keep-Alive** - 需要 picoev 支持 +5. **优化 LRU 缓存** - 减少时间调用,使用更高效的数据结构 +6. **路由预编译** - 启动时预编译所有正则表达式 + +### 低优先级 +7. **sendfile 支持** - 静态文件零拷贝传输 +8. **响应大小分级处理** - 小响应直接发送,大响应流式传输 +9. **手动内存管理** - 对热点路径进行优化 + +## 性能对比总结 + +| 特性 | veb | v-hono | 差距原因 | +|------|-----|--------|----------| +| 事件模型 | picoev | http.Server | picoev 更高效 | +| Keep-Alive | ✅ 支持 | ❌ 不支持 | http.Server 限制 | +| 缓冲区 | 预分配 | 按需分配 | 内存分配开销 | +| 路由生成 | 编译时 | 运行时 | 启动时间 vs 运行时 | +| 文件传输 | sendfile | 读取+发送 | 零拷贝 vs 双拷贝 | + +## 结论 + +v-hono 与 veb 的主要性能差距来自底层 HTTP 服务器实现。如果 v-hono 切换到 picoev,性能应该能接近甚至超过 veb。 diff --git a/examples/auth_example.v b/examples/auth_example.v index 22c775f..f8d425b 100644 --- a/examples/auth_example.v +++ b/examples/auth_example.v @@ -100,7 +100,7 @@ fn main() { }) println('🚀 用户角色菜单管理系统已启动') - println('📱 访问地址: http://localhost:3000') + println('📱 访问地址: http://127.0.0.1:3000') println('') println('📋 示例用户:') println(' 用户名: admin, 密码: admin123, 角色: admin') diff --git a/examples/basic/example.v b/examples/basic/example.v index a04fcb7..9b6a7ca 100644 --- a/examples/basic/example.v +++ b/examples/basic/example.v @@ -42,6 +42,6 @@ fn main() { return c.json('{"received": "${body}"}') }) - println('Server starting on http://localhost:3000') + println('Server starting on http://127.0.0.1:3000') app.listen(':3000') } diff --git a/examples/benchmark_veb_vs_hono.v b/examples/benchmark_veb_vs_hono.v new file mode 100644 index 0000000..b963852 --- /dev/null +++ b/examples/benchmark_veb_vs_hono.v @@ -0,0 +1,344 @@ +// veb vs v-hono 性能对比测试 +// 运行: v run benchmark_veb_vs_hono.v +// +// 测试内容: +// 1. 路由匹配性能(纯路由,不启动服务器) +// 2. 静态路由 vs 动态路由性能对比 +// 3. 缓存命中率分析 + +module main + +import time +import hono +import net.http + +// ============================================ +// 测试配置 +// ============================================ +const iterations = 10000 +const warmup_iterations = 1000 + +// 测试路径 +const static_paths = [ + '/', + '/api/health', + '/api/users', +] + +const dynamic_paths = [ + '/api/users/123', + '/api/users/456/posts', + '/api/users/789/posts/101', + '/api/categories/electronics/items/phone', +] + +// ============================================ +// 简单路由器(模拟 veb 的路由匹配方式) +// ============================================ +struct SimpleRouter { +mut: + static_routes map[string]string + dynamic_routes []DynamicRoute +} + +struct DynamicRoute { + pattern string + param_names []string +} + +fn SimpleRouter.new() SimpleRouter { + return SimpleRouter{ + static_routes: map[string]string{} + dynamic_routes: []DynamicRoute{} + } +} + +fn (mut r SimpleRouter) add_static(path string) { + r.static_routes[path] = path +} + +fn (mut r SimpleRouter) add_dynamic(pattern string, param_names []string) { + r.dynamic_routes << DynamicRoute{pattern, param_names} +} + +// 简单的动态路由匹配(模拟 veb 的线性扫描方式) +fn (r SimpleRouter) match_route(path string) (bool, map[string]string) { + // 1. 先尝试静态匹配 + if path in r.static_routes { + return true, map[string]string{} + } + + // 2. 动态路由匹配(线性扫描) + path_parts := path.split('/').filter(it.len > 0) + + for route in r.dynamic_routes { + pattern_parts := route.pattern.split('/').filter(it.len > 0) + + if path_parts.len != pattern_parts.len { + continue + } + + mut matched := true + mut params := map[string]string{} + mut param_idx := 0 + + for i, part in pattern_parts { + if part.starts_with(':') { + // 参数匹配 + if param_idx < route.param_names.len { + params[route.param_names[param_idx]] = path_parts[i] + param_idx++ + } + } else if part != path_parts[i] { + matched = false + break + } + } + + if matched { + return true, params + } + } + + return false, map[string]string{} +} + +// ============================================ +// 基准测试函数 +// ============================================ +struct BenchmarkResult { + name string + total_time_ns i64 + avg_time_ns i64 + min_time_ns i64 + max_time_ns i64 + ops_per_sec f64 + iterations int +} + +fn benchmark(name string, iterations int, f fn () bool) BenchmarkResult { + mut times := []i64{cap: iterations} + mut min_time := i64(9223372036854775807) + mut max_time := i64(0) + + // 预热 + for _ in 0 .. warmup_iterations { + f() + } + + // 正式测试 + for _ in 0 .. iterations { + sw := time.new_stopwatch() + f() + elapsed := sw.elapsed().nanoseconds() + times << elapsed + + if elapsed < min_time { + min_time = elapsed + } + if elapsed > max_time { + max_time = elapsed + } + } + + mut total := i64(0) + for t in times { + total += t + } + + avg := total / iterations + ops_per_sec := 1_000_000_000.0 / f64(avg) + + return BenchmarkResult{ + name: name + total_time_ns: total + avg_time_ns: avg + min_time_ns: min_time + max_time_ns: max_time + ops_per_sec: ops_per_sec + iterations: iterations + } +} + +fn print_result(r BenchmarkResult) { + println('┌─────────────────────────────────────────────────────────────┐') + println('│ ${r.name:-59} │') + println('├─────────────────────────────────────────────────────────────┤') + println('│ 迭代次数: ${r.iterations:-48} │') + println('│ 总耗时: ${r.total_time_ns / 1_000_000:-45} ms │') + println('│ 平均耗时: ${r.avg_time_ns:-45} ns │') + println('│ 最小耗时: ${r.min_time_ns:-45} ns │') + println('│ 最大耗时: ${r.max_time_ns:-45} ns │') + println('│ 吞吐量: ${r.ops_per_sec:-42.0} ops/s │') + println('└─────────────────────────────────────────────────────────────┘') +} + +fn print_comparison(name string, simple_result BenchmarkResult, hono_result BenchmarkResult) { + speedup := f64(simple_result.avg_time_ns) / f64(hono_result.avg_time_ns) + winner := if speedup > 1.0 { 'v-hono' } else { 'SimpleRouter' } + ratio := if speedup > 1.0 { speedup } else { 1.0 / speedup } + + println('') + println('═══════════════════════════════════════════════════════════════') + println(' ${name} 对比结果') + println('═══════════════════════════════════════════════════════════════') + println(' SimpleRouter: ${simple_result.avg_time_ns} ns/op (${simple_result.ops_per_sec:.0} ops/s)') + println(' v-hono: ${hono_result.avg_time_ns} ns/op (${hono_result.ops_per_sec:.0} ops/s)') + println(' 胜出: ${winner} (快 ${ratio:.2}x)') + println('═══════════════════════════════════════════════════════════════') +} + +// ============================================ +// 主测试函数 +// ============================================ +fn main() { + println('') + println('╔═══════════════════════════════════════════════════════════════╗') + println('║ veb vs v-hono 路由性能对比测试 ║') + println('║ (SimpleRouter 模拟 veb 的路由匹配方式) ║') + println('╠═══════════════════════════════════════════════════════════════╣') + println('║ 测试配置: ║') + println('║ - 迭代次数: ${iterations:-46} ║') + println('║ - 预热次数: ${warmup_iterations:-46} ║') + println('╚═══════════════════════════════════════════════════════════════╝') + println('') + + // 初始化 SimpleRouter + mut simple_router := SimpleRouter.new() + simple_router.add_static('/') + simple_router.add_static('/api/health') + simple_router.add_static('/api/users') + simple_router.add_dynamic('/api/users/:id', ['id']) + simple_router.add_dynamic('/api/users/:id/posts', ['id']) + simple_router.add_dynamic('/api/users/:user_id/posts/:post_id', ['user_id', 'post_id']) + simple_router.add_dynamic('/api/categories/:cat/items/:item', ['cat', 'item']) + + // 初始化 v-hono + mut hono_app := hono.Hono.new() + hono_app.get('/', fn (mut c hono.Context) http.Response { + return c.text('Hello World') + }) + hono_app.get('/api/health', fn (mut c hono.Context) http.Response { + return c.text('OK') + }) + hono_app.get('/api/users', fn (mut c hono.Context) http.Response { + return c.json('{"users": []}') + }) + hono_app.get('/api/users/:id', fn (mut c hono.Context) http.Response { + return c.json('{"id": "123"}') + }) + hono_app.get('/api/users/:id/posts', fn (mut c hono.Context) http.Response { + return c.json('{"posts": []}') + }) + hono_app.get('/api/users/:user_id/posts/:post_id', fn (mut c hono.Context) http.Response { + return c.json('{"post": {}}') + }) + hono_app.get('/api/categories/:cat/items/:item', fn (mut c hono.Context) http.Response { + return c.json('{"item": {}}') + }) + + // ============================================ + // 测试 1: 静态路由性能 + // ============================================ + println('\n【测试 1】静态路由匹配性能') + println('─────────────────────────────────────────────────────────────────') + + simple_static_result := benchmark('SimpleRouter 静态路由', iterations, fn [simple_router] () bool { + for path in static_paths { + simple_router.match_route(path) + } + return true + }) + print_result(simple_static_result) + + hono_static_result := benchmark('v-hono 静态路由', iterations, fn [mut hono_app] () bool { + for path in static_paths { + hono_app.fast_router.match_route('GET', path) or { continue } + } + return true + }) + print_result(hono_static_result) + + print_comparison('静态路由', simple_static_result, hono_static_result) + + // ============================================ + // 测试 2: 动态路由性能 + // ============================================ + println('\n【测试 2】动态路由匹配性能') + println('─────────────────────────────────────────────────────────────────') + + simple_dynamic_result := benchmark('SimpleRouter 动态路由', iterations, fn [simple_router] () bool { + for path in dynamic_paths { + simple_router.match_route(path) + } + return true + }) + print_result(simple_dynamic_result) + + hono_dynamic_result := benchmark('v-hono 动态路由', iterations, fn [mut hono_app] () bool { + for path in dynamic_paths { + hono_app.fast_router.match_route('GET', path) or { continue } + } + return true + }) + print_result(hono_dynamic_result) + + print_comparison('动态路由', simple_dynamic_result, hono_dynamic_result) + + // ============================================ + // 测试 3: 混合路由性能 + // ============================================ + println('\n【测试 3】混合路由匹配性能(静态 + 动态)') + println('─────────────────────────────────────────────────────────────────') + + mut all_paths := []string{} + all_paths << static_paths + all_paths << dynamic_paths + + simple_mixed_result := benchmark('SimpleRouter 混合路由', iterations, fn [simple_router, all_paths] () bool { + for path in all_paths { + simple_router.match_route(path) + } + return true + }) + print_result(simple_mixed_result) + + hono_mixed_result := benchmark('v-hono 混合路由', iterations, fn [mut hono_app, all_paths] () bool { + for path in all_paths { + hono_app.fast_router.match_route('GET', path) or { continue } + } + return true + }) + print_result(hono_mixed_result) + + print_comparison('混合路由', simple_mixed_result, hono_mixed_result) + + // ============================================ + // 路由器统计信息 + // ============================================ + println('\n【路由器统计信息】') + println('─────────────────────────────────────────────────────────────────') + + static_count, dynamic_count, cache_count, _ := hono_app.get_router_stats() + println('v-hono FastRouter:') + println(' - 静态路由数: ${static_count}') + println(' - 动态路由数: ${dynamic_count}') + println(' - 缓存条目数: ${cache_count}') + + println('') + println('SimpleRouter:') + println(' - 静态路由数: ${simple_router.static_routes.len}') + println(' - 动态路由数: ${simple_router.dynamic_routes.len}') + + // ============================================ + // 总结 + // ============================================ + println('\n') + println('╔═══════════════════════════════════════════════════════════════╗') + println('║ 测试总结 ║') + println('╠═══════════════════════════════════════════════════════════════╣') + println('║ 1. 静态路由: SimpleRouter 使用 map 直接查找,通常更快 ║') + println('║ 2. 动态路由: v-hono 使用 Trie + 缓存,性能更稳定 ║') + println('║ 3. v-hono 的优势在于动态路由性能一致,不随复杂度增加而下降 ║') + println('╚═══════════════════════════════════════════════════════════════╝') +} diff --git a/examples/http_benchmark.v b/examples/http_benchmark.v new file mode 100644 index 0000000..970dd99 --- /dev/null +++ b/examples/http_benchmark.v @@ -0,0 +1,237 @@ +// HTTP 压测工具 - veb vs v-hono 性能对比 +// 运行: v run http_benchmark.v +// +// 使用前请先启动服务器: +// 终端1: v run server_veb.v (端口 8080) +// 终端2: v run server_hono.v (端口 8081) + +module main + +import net.http +import time + +// 测试配置 +const veb_base_url = 'http://127.0.0.1:8080' +const hono_base_url = 'http://127.0.0.1:8081' +const requests_per_endpoint = 200 + +// 测试端点 +struct Endpoint { + path string + name string +} + +const test_endpoints = [ + Endpoint{path: '/', name: '首页 (静态)'}, + Endpoint{path: '/api/health', name: '健康检查 (静态)'}, + Endpoint{path: '/api/users', name: '用户列表 (静态)'}, + Endpoint{path: '/api/users/123', name: '单个用户 (动态)'}, + Endpoint{path: '/api/users/456/posts', name: '用户帖子 (动态)'}, +] + +struct BenchmarkResult { + endpoint string + total_requests int + success_count int + error_count int + total_time_ms i64 + avg_time_ms f64 + min_time_ms f64 + max_time_ms f64 + rps f64 +} + +fn main() { + println('') + println('╔═══════════════════════════════════════════════════════════════╗') + println('║ HTTP 压测工具 - veb vs v-hono 性能对比 ║') + println('╠═══════════════════════════════════════════════════════════════╣') + println('║ 请确保已启动以下服务器: ║') + println('║ - veb: v run server_veb.v (端口 8080) ║') + println('║ - v-hono: v run server_hono.v (端口 8081) ║') + println('╚═══════════════════════════════════════════════════════════════╝') + println('') + + // 检查服务器是否可用 + println('检查服务器状态...') + veb_available := check_server(veb_base_url) + hono_available := check_server(hono_base_url) + + if !veb_available { + println('⚠️ veb 服务器 (${veb_base_url}) 不可用') + } else { + println('✅ veb 服务器 (${veb_base_url}) 已就绪') + } + + if !hono_available { + println('⚠️ v-hono 服务器 (${hono_base_url}) 不可用') + } else { + println('✅ v-hono 服务器 (${hono_base_url}) 已就绪') + } + + if !veb_available && !hono_available { + println('') + println('❌ 没有可用的服务器,请先启动服务器后再运行测试') + return + } + + println('') + println('开始压测 (每端点 ${requests_per_endpoint} 次请求)...') + println('─────────────────────────────────────────────────────────────────') + + mut veb_results := []BenchmarkResult{} + mut hono_results := []BenchmarkResult{} + + for endpoint in test_endpoints { + println('') + println('测试端点: ${endpoint.name} (${endpoint.path})') + + if veb_available { + result := run_benchmark('veb', veb_base_url, endpoint) + veb_results << result + print_single_result('veb', result) + } + + if hono_available { + result := run_benchmark('v-hono', hono_base_url, endpoint) + hono_results << result + print_single_result('v-hono', result) + } + } + + // 打印对比结果 + if veb_available && hono_available { + print_comparison_table(veb_results, hono_results) + } + + // 打印总结 + print_summary(veb_results, hono_results, veb_available, hono_available) +} + +fn check_server(base_url string) bool { + http.get(base_url) or { return false } + return true +} + +fn run_benchmark(name string, base_url string, endpoint Endpoint) BenchmarkResult { + url := '${base_url}${endpoint.path}' + + mut success_count := 0 + mut error_count := 0 + mut min_time := f64(999999.0) + mut max_time := f64(0.0) + mut total_req_time := f64(0.0) + + start_time := time.now() + + for _ in 0 .. requests_per_endpoint { + req_start := time.now() + + resp := http.get(url) or { + error_count++ + continue + } + + req_time := f64(time.since(req_start).microseconds()) / 1000.0 + + if resp.status_code >= 200 && resp.status_code < 300 { + success_count++ + } else { + error_count++ + } + + total_req_time += req_time + if req_time < min_time { + min_time = req_time + } + if req_time > max_time { + max_time = req_time + } + } + + total_time := time.since(start_time) + total_time_ms := total_time.milliseconds() + total_requests := success_count + error_count + avg_time := if total_requests > 0 { total_req_time / f64(total_requests) } else { 0.0 } + rps := if total_time_ms > 0 { f64(total_requests) * 1000.0 / f64(total_time_ms) } else { 0.0 } + + return BenchmarkResult{ + endpoint: endpoint.name + total_requests: total_requests + success_count: success_count + error_count: error_count + total_time_ms: total_time_ms + avg_time_ms: avg_time + min_time_ms: min_time + max_time_ms: max_time + rps: rps + } +} + +fn print_single_result(name string, r BenchmarkResult) { + println(' ${name}: ${r.rps:.0} req/s | avg: ${r.avg_time_ms:.2}ms | min: ${r.min_time_ms:.2}ms | max: ${r.max_time_ms:.2}ms | 成功: ${r.success_count}/${r.total_requests}') +} + +fn print_comparison_table(veb_results []BenchmarkResult, hono_results []BenchmarkResult) { + println('') + println('╔═══════════════════════════════════════════════════════════════════════════════════════╗') + println('║ 性能对比表 ║') + println('╠═══════════════════════════════════════════════════════════════════════════════════════╣') + println('║ 端点 │ veb (req/s) │ v-hono (req/s) │ 差异 │ 胜出 ║') + println('╠═══════════════════════════════════════════════════════════════════════════════════════╣') + + for i, veb_r in veb_results { + if i >= hono_results.len { + break + } + hono_r := hono_results[i] + + diff := hono_r.rps - veb_r.rps + diff_pct := if veb_r.rps > 0 { (diff / veb_r.rps) * 100.0 } else { 0.0 } + winner := if hono_r.rps > veb_r.rps { 'v-hono' } else { 'veb' } + + name := veb_r.endpoint + veb_rps := '${veb_r.rps:.0}' + hono_rps := '${hono_r.rps:.0}' + diff_str := if diff > 0 { '+${diff_pct:.1}%' } else { '${diff_pct:.1}%' } + + println('║ ${name:-23} │ ${veb_rps:-12} │ ${hono_rps:-14} │ ${diff_str:-9} │ ${winner:-14} ║') + } + + println('╚═══════════════════════════════════════════════════════════════════════════════════════╝') +} + +fn print_summary(veb_results []BenchmarkResult, hono_results []BenchmarkResult, veb_available bool, hono_available bool) { + println('') + println('╔═══════════════════════════════════════════════════════════════╗') + println('║ 测试总结 ║') + println('╠═══════════════════════════════════════════════════════════════╣') + + if veb_available && veb_results.len > 0 { + mut total_rps := f64(0.0) + for r in veb_results { + total_rps += r.rps + } + avg_rps := total_rps / f64(veb_results.len) + println('║ veb 平均吞吐量: ${avg_rps:-40.0} req/s ║') + } + + if hono_available && hono_results.len > 0 { + mut total_rps := f64(0.0) + for r in hono_results { + total_rps += r.rps + } + avg_rps := total_rps / f64(hono_results.len) + println('║ v-hono 平均吞吐量: ${avg_rps:-40.0} req/s ║') + } + + println('╠═══════════════════════════════════════════════════════════════╣') + println('║ 测试配置: ║') + println('║ - 每端点请求数: ${requests_per_endpoint:-44} ║') + println('╚═══════════════════════════════════════════════════════════════╝') + + println('') + println('提示: 使用 wrk 或 ab 可以获得更准确的压测结果:') + println(' wrk -t4 -c100 -d10s http://127.0.0.1:8080/') + println(' wrk -t4 -c100 -d10s http://127.0.0.1:8081/') +} diff --git a/examples/middleware/example.v b/examples/middleware/example.v index c950833..03490b7 100644 --- a/examples/middleware/example.v +++ b/examples/middleware/example.v @@ -34,6 +34,6 @@ fn main() { return c.json('{"data": "some data"}') }) - println('Server starting on http://localhost:3000') + println('Server starting on http://127.0.0.1:3000') app.listen(':3000') } diff --git a/examples/redirect_demo.v b/examples/redirect_demo.v index c9a0398..329ecbb 100644 --- a/examples/redirect_demo.v +++ b/examples/redirect_demo.v @@ -62,7 +62,7 @@ fn main() { return c.html('
API available at /api
') }) - println('Server starting on http://localhost:3000') + println('Server starting on http://127.0.0.1:3000') println('Routes:') println(' GET / - Welcome page') println(' GET /api/users - List users') diff --git a/examples/route_grouping/route_grouping_example.v b/examples/route_grouping/route_grouping_example.v index 5b29aa9..d0ad7fb 100644 --- a/examples/route_grouping/route_grouping_example.v +++ b/examples/route_grouping/route_grouping_example.v @@ -211,7 +211,7 @@ app.route("/api/books", mut books) // 打印路由信息 fn print_routes_info() { println('') - println('📍 服务器地址: http://localhost:8080') + println('📍 服务器地址: http://127.0.0.1:8080') println('') println('📚 Books API (/api/books):') println(' GET /api/books - 获取所有书籍') diff --git a/examples/route_grouping/route_grouping_example_v2.v b/examples/route_grouping/route_grouping_example_v2.v index c251f17..d4a7911 100644 --- a/examples/route_grouping/route_grouping_example_v2.v +++ b/examples/route_grouping/route_grouping_example_v2.v @@ -233,16 +233,16 @@ app.route(\'/api/books\', mut books)# 测试 all() 方法 -curl http://localhost:8080/echo -curl -X POST http://localhost:8080/echo -curl -X PUT http://localhost:8080/echo +curl http://127.0.0.1:8080/echo +curl -X POST http://127.0.0.1:8080/echo +curl -X PUT http://127.0.0.1:8080/echo # 测试自定义 404 -curl http://localhost:8080/not-exist +curl http://127.0.0.1:8080/not-exist # 测试 Admin 认证中间件 -curl http://localhost:8080/admin # 返回 401 -curl -H "Authorization: Bearer admin-token" http://localhost:8080/admin # 成功+curl http://127.0.0.1:8080/admin # 返回 401 +curl -H "Authorization: Bearer admin-token" http://127.0.0.1:8080/admin # 成功