| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- package main
-
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "testing"
- "time"
-
- "git.x2erp.com/qdy/go-svc-code/internal/opencode"
- )
-
- // TestMessageResponseTimeComparison 性能对比测试:比较8787端口和8020端口的历史消息加载性能
- func TestMessageResponseTimeComparison(t *testing.T) {
- // 测试会话ID - 使用现有会话
- sessionID := "ses_3aa9a94dfffeLIdWcLHCG97m7z"
- t.Logf("🚀 开始性能对比测试")
- t.Logf("测试会话ID: %s", sessionID)
-
- // 1. 测试8787端口(直接opencode服务)
- t.Run("DirectOpenCode_8787", func(t *testing.T) {
- directURL := "http://localhost:8787"
-
- // 检查服务是否可用
- if !isServiceRunningForMessages(t, directURL) {
- t.Skipf("8787端口服务不可用,跳过测试")
- return
- }
-
- t.Logf("📊 测试8787端口直接访问...")
-
- // 测试不同消息数量的加载性能
- testCases := []struct {
- name string
- limit int
- repeat int // 重复次数
- }{
- {"limit_10", 10, 5},
- {"limit_50", 50, 5},
- {"limit_100", 100, 5},
- {"no_limit", 0, 3},
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- var totalDuration time.Duration
- var successes int
- var firstByteTimes []time.Duration
-
- for i := 0; i < tc.repeat; i++ {
- duration, firstByte, err := measureDirectRequest(directURL, sessionID, tc.limit)
- if err != nil {
- t.Logf("⚠️ 第%d次请求失败: %v", i+1, err)
- continue
- }
-
- totalDuration += duration
- firstByteTimes = append(firstByteTimes, firstByte)
- successes++
-
- // 请求间短暂延迟
- time.Sleep(100 * time.Millisecond)
- }
-
- if successes > 0 {
- avgDuration := totalDuration / time.Duration(successes)
- var avgFirstByte time.Duration
- for _, fb := range firstByteTimes {
- avgFirstByte += fb
- }
- avgFirstByte /= time.Duration(len(firstByteTimes))
-
- t.Logf("✅ %s: 成功%d/%d次, 平均总时间: %v, 平均首字节: %v",
- tc.name, successes, tc.repeat, avgDuration, avgFirstByte)
- } else {
- t.Errorf("❌ %s: 所有请求都失败", tc.name)
- }
- })
- }
- })
-
- // 2. 测试8020端口(svc-code代理)
- t.Run("SvcCodeProxy_8020", func(t *testing.T) {
- proxyURL := "http://localhost:8020"
-
- // 检查服务是否可用
- if !isServiceRunningForMessages(t, proxyURL) {
- t.Skipf("8020端口服务不可用,跳过测试")
- return
- }
-
- // 获取认证token
- token, err := loginAndGetTokenForMessagesTest(t, proxyURL)
- if err != nil {
- t.Skipf("获取认证token失败: %v,跳过8020端口测试", err)
- return
- }
-
- t.Logf("📊 测试8020端口代理访问...")
- t.Logf("Token获取成功(前20位): %s...", token[:minForMessagesTest(20, len(token))])
-
- // 测试不同消息数量的加载性能
- testCases := []struct {
- name string
- limit int
- repeat int // 重复次数
- }{
- {"limit_10", 10, 5},
- {"limit_50", 50, 5},
- {"limit_100", 100, 5},
- {"no_limit", 0, 3},
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- var totalDuration time.Duration
- var successes int
- var firstByteTimes []time.Duration
-
- for i := 0; i < tc.repeat; i++ {
- duration, firstByte, err := measureProxyRequest(proxyURL, sessionID, token, tc.limit)
- if err != nil {
- t.Logf("⚠️ 第%d次请求失败: %v", i+1, err)
- continue
- }
-
- totalDuration += duration
- firstByteTimes = append(firstByteTimes, firstByte)
- successes++
-
- // 请求间短暂延迟
- time.Sleep(100 * time.Millisecond)
- }
-
- if successes > 0 {
- avgDuration := totalDuration / time.Duration(successes)
- var avgFirstByte time.Duration
- for _, fb := range firstByteTimes {
- avgFirstByte += fb
- }
- avgFirstByte /= time.Duration(len(firstByteTimes))
-
- t.Logf("✅ %s: 成功%d/%d次, 平均总时间: %v, 平均首字节: %v",
- tc.name, successes, tc.repeat, avgDuration, avgFirstByte)
- } else {
- t.Errorf("❌ %s: 所有请求都失败", tc.name)
- }
- })
- }
- })
- }
-
- // TestPerformanceSummary 性能对比总结
- func TestPerformanceSummary(t *testing.T) {
- sessionID := "ses_3aa9a94dfffeLIdWcLHCG97m7z"
-
- // 只测试limit=50的情况,进行更详细的对比
- t.Logf("📈 性能对比总结测试 (limit=50)")
-
- // 测试8787端口
- directTimes := []time.Duration{}
- directFirstBytes := []time.Duration{}
- directURL := "http://localhost:8787"
-
- if isServiceRunningForMessages(t, directURL) {
- t.Log("🔍 测试8787端口...")
- for i := 0; i < 10; i++ {
- duration, firstByte, err := measureDirectRequest(directURL, sessionID, 50)
- if err != nil {
- t.Logf("⚠️ 8787端口第%d次请求失败: %v", i+1, err)
- continue
- }
- directTimes = append(directTimes, duration)
- directFirstBytes = append(directFirstBytes, firstByte)
- time.Sleep(50 * time.Millisecond)
- }
- }
-
- // 测试8020端口
- proxyTimes := []time.Duration{}
- proxyFirstBytes := []time.Duration{}
- proxyURL := "http://localhost:8020"
- token := ""
-
- if isServiceRunningForMessages(t, proxyURL) {
- t.Log("🔍 测试8020端口...")
- token, _ = loginAndGetTokenForMessagesTest(t, proxyURL)
- if token != "" {
- for i := 0; i < 10; i++ {
- duration, firstByte, err := measureProxyRequest(proxyURL, sessionID, token, 50)
- if err != nil {
- t.Logf("⚠️ 8020端口第%d次请求失败: %v", i+1, err)
- continue
- }
- proxyTimes = append(proxyTimes, duration)
- proxyFirstBytes = append(proxyFirstBytes, firstByte)
- time.Sleep(50 * time.Millisecond)
- }
- }
- }
-
- // 分析结果
- if len(directTimes) > 0 && len(proxyTimes) > 0 {
- directAvg := averageDuration(directTimes)
- directFBAvg := averageDuration(directFirstBytes)
- proxyAvg := averageDuration(proxyTimes)
- proxyFBAvg := averageDuration(proxyFirstBytes)
-
- diff := proxyAvg - directAvg
- diffPercent := float64(diff) / float64(directAvg) * 100
-
- t.Logf("\n📊 性能对比结果:")
- t.Logf(" 8787端口(直接): 平均总时间=%v, 平均首字节=%v", directAvg, directFBAvg)
- t.Logf(" 8020端口(代理): 平均总时间=%v, 平均首字节=%v", proxyAvg, proxyFBAvg)
- t.Logf(" 差异: %v (%.1f%%)", diff, diffPercent)
-
- if diffPercent > 20 {
- t.Logf("⚠️ 8020端口比8787端口慢%.1f%%,可能存在代理开销", diffPercent)
- } else if diffPercent > 50 {
- t.Logf("❌ 8020端口性能显著下降,建议优化代理逻辑")
- } else if diffPercent < -10 {
- t.Logf("✅ 8020端口性能更好,可能包含缓存优化")
- } else {
- t.Logf("✅ 两端性能接近,差异在可接受范围内")
- }
- } else {
- t.Logf("⚠️ 无法完成完整对比,部分端口数据缺失")
- if len(directTimes) == 0 {
- t.Logf(" - 8787端口无有效数据")
- }
- if len(proxyTimes) == 0 {
- t.Logf(" - 8020端口无有效数据")
- }
- }
- }
-
- // measureDirectRequest 测量直接访问8787端口的请求性能
- func measureDirectRequest(baseURL, sessionID string, limit int) (totalDuration, firstByteDuration time.Duration, err error) {
- // 构造URL
- url := fmt.Sprintf("%s/session/%s/message", baseURL, sessionID)
- if limit > 0 {
- url = fmt.Sprintf("%s?limit=%d", url, limit)
- }
-
- client := &http.Client{
- Timeout: 30 * time.Second,
- }
-
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- return 0, 0, fmt.Errorf("创建请求失败: %w", err)
- }
- req.Header.Set("Accept", "application/json")
-
- startTime := time.Now()
-
- // 监听响应首字节
- req.Header.Set("Accept-Encoding", "identity") // 禁用压缩以便准确测量
-
- resp, err := client.Do(req)
- if err != nil {
- return 0, 0, fmt.Errorf("HTTP请求失败: %w", err)
- }
- defer resp.Body.Close()
-
- // 记录首字节到达时间
- firstByteTime := time.Now()
- firstByteDuration = firstByteTime.Sub(startTime)
-
- // 读取完整响应体
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return 0, 0, fmt.Errorf("读取响应体失败: %w", err)
- }
-
- totalDuration = time.Since(startTime)
-
- // 验证响应状态
- if resp.StatusCode != http.StatusOK {
- return 0, 0, fmt.Errorf("状态码错误: %d, 响应: %s", resp.StatusCode, string(body[:minForMessagesTest(200, len(body))]))
- }
-
- // 验证响应格式
- var messages []opencode.SessionMessage
- if err := json.Unmarshal(body, &messages); err != nil {
- return 0, 0, fmt.Errorf("解析响应失败: %w", err)
- }
-
- return totalDuration, firstByteDuration, nil
- }
-
- // measureProxyRequest 测量通过8020端口代理的请求性能
- func measureProxyRequest(baseURL, sessionID, token string, limit int) (totalDuration, firstByteDuration time.Duration, err error) {
- // 构造URL
- url := fmt.Sprintf("%s/api/session/messages", baseURL)
-
- // 创建请求体
- requestBody := map[string]interface{}{
- "sessionID": sessionID,
- }
- if limit > 0 {
- requestBody["limit"] = limit
- }
-
- jsonBody, err := json.Marshal(requestBody)
- if err != nil {
- return 0, 0, fmt.Errorf("编码请求体失败: %w", err)
- }
-
- client := &http.Client{
- Timeout: 30 * time.Second,
- }
-
- req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
- if err != nil {
- return 0, 0, fmt.Errorf("创建请求失败: %w", err)
- }
-
- // 添加认证头
- req.Header.Set("Authorization", "Bearer "+token)
- req.Header.Set("Accept", "application/json")
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Accept-Encoding", "identity")
-
- startTime := time.Now()
-
- resp, err := client.Do(req)
- if err != nil {
- return 0, 0, fmt.Errorf("HTTP请求失败: %w", err)
- }
- defer resp.Body.Close()
-
- // 记录首字节到达时间
- firstByteTime := time.Now()
- firstByteDuration = firstByteTime.Sub(startTime)
-
- // 读取完整响应体
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return 0, 0, fmt.Errorf("读取响应体失败: %w", err)
- }
-
- totalDuration = time.Since(startTime)
-
- // 验证响应状态
- if resp.StatusCode != http.StatusOK {
- return 0, 0, fmt.Errorf("状态码错误: %d, 响应: %s", resp.StatusCode, string(body[:minForMessagesTest(200, len(body))]))
- }
-
- // 验证响应格式
- var result struct {
- Success bool `json:"success"`
- Message string `json:"message"`
- Data struct {
- Messages []opencode.SessionMessage `json:"messages"`
- Count int `json:"count"`
- } `json:"data"`
- }
-
- if err := json.Unmarshal(body, &result); err != nil {
- return 0, 0, fmt.Errorf("解析响应失败: %w", err)
- }
-
- if !result.Success {
- return 0, 0, fmt.Errorf("API调用失败: %s", result.Message)
- }
-
- return totalDuration, firstByteDuration, nil
- }
-
- // averageDuration 计算平均时长
- func averageDuration(durations []time.Duration) time.Duration {
- if len(durations) == 0 {
- return 0
- }
- var total time.Duration
- for _, d := range durations {
- total += d
- }
- return total / time.Duration(len(durations))
- }
|