package main import ( "encoding/json" "fmt" "io" "net/http" "runtime" "sync" "sync/atomic" "time" ) func main() { runtime.GOMAXPROCS(runtime.NumCPU()) fmt.Println("==========================================") fmt.Println(" ๐Ÿ”ฅ HoTime ๆž้™ๅŽ‹ๅŠ›ๆต‹่ฏ• ๐Ÿ”ฅ") fmt.Println("==========================================") fmt.Printf("CPU ๆ ธๅฟƒๆ•ฐ: %d\n", runtime.NumCPU()) fmt.Println() baseURL := "http://127.0.0.1:8081/app/test/hello" // ๆฃ€ๆŸฅๆœๅŠก fmt.Println("ๆญฃๅœจๆฃ€ๆŸฅๆœๅŠกๆ˜ฏๅฆๅฏ็”จ...") client := &http.Client{Timeout: 3 * time.Second} resp, err := client.Get(baseURL) if err != nil || resp.StatusCode != 200 { fmt.Println("โŒ ๆœๅŠกไธๅฏ็”จ๏ผŒ่ฏทๅ…ˆๅฏๅŠจ็คบไพ‹ๅบ”็”จ") return } resp.Body.Close() fmt.Println("โœ… ๆœๅŠกๅทฒๅฐฑ็ปช") fmt.Println() // ้ข„็ƒญ fmt.Println("๐Ÿ”„ ้ข„็ƒญไธญ (5็ง’)...") doWarmup(baseURL, 100, 5*time.Second) fmt.Println("โœ… ้ข„็ƒญๅฎŒๆˆ") fmt.Println() // ๆž้™ๆต‹่ฏ•้…็ฝฎ concurrencyLevels := []int{500, 1000, 2000, 5000, 10000} testDuration := 15 * time.Second var maxQPS float64 var maxConcurrency int for i, concurrency := range concurrencyLevels { fmt.Printf("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n") fmt.Printf("ใ€ๆž้™ๆต‹่ฏ• %dใ€‘ๅนถๅ‘ๆ•ฐ: %d, ๆŒ็ปญๆ—ถ้—ด: %v\n", i+1, concurrency, testDuration) fmt.Printf("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n") total, success, failed, qps, minLat, avgLat, maxLat, p50, p90, p99 := doBenchmark(baseURL, concurrency, testDuration) successRate := float64(success) / float64(total) * 100 fmt.Printf("ๆ€ป่ฏทๆฑ‚ๆ•ฐ: %d\n", total) fmt.Printf("ๆˆๅŠŸ่ฏทๆฑ‚: %d\n", success) fmt.Printf("ๅคฑ่ดฅ่ฏทๆฑ‚: %d\n", failed) fmt.Printf("ๆˆๅŠŸ็އ: %.2f%%\n", successRate) fmt.Printf("QPS: %.2f ่ฏทๆฑ‚/็ง’\n", qps) fmt.Println("------------------------------------------") fmt.Printf("ๆœ€ๅฐๅปถ่ฟŸ: %v\n", time.Duration(minLat)) fmt.Printf("ๅนณๅ‡ๅปถ่ฟŸ: %v\n", time.Duration(avgLat)) fmt.Printf("P50ๅปถ่ฟŸ: %v\n", time.Duration(p50)) fmt.Printf("P90ๅปถ่ฟŸ: %v\n", time.Duration(p90)) fmt.Printf("P99ๅปถ่ฟŸ: %v\n", time.Duration(p99)) fmt.Printf("ๆœ€ๅคงๅปถ่ฟŸ: %v\n", time.Duration(maxLat)) // ๆ€ง่ƒฝ่ฏ„็บง fmt.Print("\nๆ€ง่ƒฝ่ฏ„็บง: ") switch { case qps >= 200000: fmt.Println("๐Ÿ† ๅ“่ถŠ (QPS >= 200K)") case qps >= 100000: fmt.Println("๐Ÿš€ ไผ˜็ง€ (QPS >= 100K)") case qps >= 50000: fmt.Println("โญ ่‰ฏๅฅฝ (QPS >= 50K)") case qps >= 20000: fmt.Println("๐Ÿ‘ ไธญไธŠ (QPS >= 20K)") case qps >= 10000: fmt.Println("๐Ÿ“Š ไธญ็ญ‰ (QPS >= 10K)") default: fmt.Println("โš ๏ธ ไธ€่ˆฌ (QPS < 10K)") } if qps > maxQPS { maxQPS = qps maxConcurrency = concurrency } // ๆฃ€ๆŸฅๆ˜ฏๅฆ่พพๅˆฐ็“ถ้ขˆ if successRate < 95 { fmt.Println("\nโš ๏ธ ๆˆๅŠŸ็އไฝŽไบŽ95%๏ผŒๅทฒ่พพๅˆฐๆœๅŠกๆž้™๏ผ") break } if avgLat > int64(100*time.Millisecond) { fmt.Println("\nโš ๏ธ ๅนณๅ‡ๅปถ่ฟŸ่ถ…่ฟ‡100ms๏ผŒๅทฒ่พพๅˆฐๆœๅŠกๆž้™๏ผ") break } fmt.Println() if i < len(concurrencyLevels)-1 { fmt.Println("ๅ†ทๅด 5 ็ง’...") time.Sleep(5 * time.Second) fmt.Println() } } // ๆœ€็ปˆๆŠฅๅ‘Š fmt.Println() fmt.Println("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•") fmt.Println(" ๐Ÿ“Š ๆž้™ๆต‹่ฏ•ๆ€ป็ป“") fmt.Println("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•") fmt.Printf("ๆœ€้ซ˜ QPS: %.2f ่ฏทๆฑ‚/็ง’\n", maxQPS) fmt.Printf("ๆœ€ไฝณๅนถๅ‘ๆ•ฐ: %d\n", maxConcurrency) fmt.Println() // ๅนถๅ‘็”จๆˆทไผฐ็ฎ— fmt.Println("๐Ÿ“ˆ ๅนถๅ‘็”จๆˆทๆ•ฐไผฐ็ฎ—:") fmt.Println("------------------------------------------") fmt.Printf("้ซ˜้ข‘ไบคไบ’(1็ง’/ๆฌก): ~%d ็”จๆˆท\n", int(maxQPS*1)) fmt.Printf("ๆดป่ทƒ็”จๆˆท(5็ง’/ๆฌก): ~%d ็”จๆˆท\n", int(maxQPS*5)) fmt.Printf("ๆ™ฎ้€šๆต่งˆ(10็ง’/ๆฌก): ~%d ็”จๆˆท\n", int(maxQPS*10)) fmt.Printf("ไฝŽ้ข‘่ฎฟ้—ฎ(30็ง’/ๆฌก): ~%d ็”จๆˆท\n", int(maxQPS*30)) fmt.Println() fmt.Println("๐Ÿ’ก ็”Ÿไบง็Žฏๅขƒๅปบ่ฎฎไฟ็•™ 30-50% ๆ€ง่ƒฝไฝ™้‡") fmt.Printf(" ๅฎ‰ๅ…จๅนถๅ‘็”จๆˆทๆ•ฐ: %d - %d (ๆ™ฎ้€šๆต่งˆๅœบๆ™ฏ)\n", int(maxQPS*10*0.5), int(maxQPS*10*0.7)) } func doWarmup(url string, concurrency int, duration time.Duration) { client := &http.Client{ Timeout: 5 * time.Second, Transport: &http.Transport{ MaxIdleConns: concurrency * 2, MaxIdleConnsPerHost: concurrency * 2, }, } done := make(chan struct{}) var wg sync.WaitGroup for i := 0; i < concurrency; i++ { wg.Add(1) go func() { defer wg.Done() for { select { case <-done: return default: resp, err := client.Get(url) if err == nil { io.Copy(io.Discard, resp.Body) resp.Body.Close() } } } }() } time.Sleep(duration) close(done) wg.Wait() } func doBenchmark(url string, concurrency int, duration time.Duration) (total, success, failed int64, qps float64, minLat, avgLat, maxLat, p50, p90, p99 int64) { var totalLatency int64 minLat = int64(time.Hour) var mu sync.Mutex // ้‡‡ๆ ทๆ”ถ้›†ๅ™จ latencies := make([]int64, 0, 50000) var latMu sync.Mutex client := &http.Client{ Timeout: 10 * time.Second, Transport: &http.Transport{ MaxIdleConns: concurrency * 2, MaxIdleConnsPerHost: concurrency * 2, MaxConnsPerHost: concurrency * 2, IdleConnTimeout: 90 * time.Second, DisableCompression: true, }, } done := make(chan struct{}) var wg sync.WaitGroup startTime := time.Now() for i := 0; i < concurrency; i++ { wg.Add(1) go func() { defer wg.Done() for { select { case <-done: return default: reqStart := time.Now() ok := doRequest(client, url) lat := time.Since(reqStart).Nanoseconds() atomic.AddInt64(&total, 1) atomic.AddInt64(&totalLatency, lat) if ok { atomic.AddInt64(&success, 1) } else { atomic.AddInt64(&failed, 1) } // ้‡‡ๆ ท (ๆฏ50ไธช้‡‡1ไธช) if atomic.LoadInt64(&total)%50 == 0 { latMu.Lock() latencies = append(latencies, lat) latMu.Unlock() } mu.Lock() if lat < minLat { minLat = lat } if lat > maxLat { maxLat = lat } mu.Unlock() } } }() } time.Sleep(duration) close(done) wg.Wait() totalDuration := time.Since(startTime) if total > 0 { avgLat = totalLatency / total qps = float64(total) / totalDuration.Seconds() } // ่ฎก็ฎ—็™พๅˆ†ไฝ if len(latencies) > 0 { p50 = getPercentile(latencies, 0.50) p90 = getPercentile(latencies, 0.90) p99 = getPercentile(latencies, 0.99) } return } func doRequest(client *http.Client, url string) bool { resp, err := client.Get(url) if err != nil { return false } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return false } if resp.StatusCode != 200 { return false } var result map[string]interface{} if err := json.Unmarshal(body, &result); err != nil { return false } if status, ok := result["status"].(float64); !ok || status != 0 { return false } return true } func getPercentile(arr []int64, p float64) int64 { n := len(arr) if n == 0 { return 0 } // ๅคๅˆถไธ€ไปฝ้ฟๅ…ไฟฎๆ”นๅŽŸๆ•ฐ็ป„ tmp := make([]int64, n) copy(tmp, arr) idx := int(float64(n) * p) if idx >= n { idx = n - 1 } return quickSelect(tmp, idx) } func quickSelect(arr []int64, k int) int64 { if len(arr) == 1 { return arr[0] } pivot := arr[len(arr)/2] var left, right, equal []int64 for _, v := range arr { if v < pivot { left = append(left, v) } else if v > pivot { right = append(right, v) } else { equal = append(equal, v) } } if k < len(left) { return quickSelect(left, k) } else if k < len(left)+len(equal) { return pivot } return quickSelect(right, k-len(left)-len(equal)) }