Hero Image
更強大的 Go 執行追蹤能力

更強大的 Go 執行追蹤能力 runtime/trace golang.org/x/exp/trace 飛行記錄(flight recording) // 設定飛行記錄器 fr := trace.NewFlightRecorder() fr.Start() // 設定並啟動 HTTP 伺服器 var once sync.Once http.HandleFunc("/my-endpoint", func(w http.ResponseWriter, r *http.Request) { start := time.Now() // 做些事情 doWork(w, r) // 碰到長耗時請求就來個快照 if time.Since(start) > 300*time.Millisecond { // 這裡為了簡化只做一次,實際上你可以做多次 once.Do(func() { // 擷取快照 var b bytes.Buffer _, err = fr.WriteTo(&b) if err != nil { log.Print(err) return } // 把快照寫入檔案 if err := os.WriteFile("trace.out", b.Bytes(), 0o755); err != nil { log.Print(err) return } }) } }) log.Fatal(http.ListenAndServe(":8080", nil)) 追蹤讀取器 API // 開始從標準輸入讀取追蹤資料。 r, err := trace.NewReader(os.Stdin) if err != nil { log.Fatal(err) } var blocked int var blockedOnNetwork int for { // 讀取事件 ev, err := r.ReadEvent() if err == io.EOF { break } else if err != nil { log.Fatal(err) } // 處理它 if ev.Kind() == trace.EventStateTransition { st := ev.StateTransition() if st.Resource.Kind == trace.ResourceGoroutine { id := st.Resource.Goroutine() from, to := st.GoroutineTransition() // 查找阻塞的 goroutine 並統計 if from.Executing() && to == trace.GoWaiting { blocked++ if strings.Contains(st.Reason, "network") { blockedOnNetwork++ } } } } } // 印出所需數值 p := 100 * float64(blockedOnNetwork) / float64(blocked) fmt.Printf("%2.3f%% instances of goroutines blocking were to block on the network\n", p)

Hero Image
Golang tips

GO 编程模式:切片,接口,时间和性能 Go 语言是一个高性能的语言,但并不是说这样我们就不用关心性能了,我们还是需要关心的。下面是一个在编程方面和性能相关的提示。 如果需要把数字转字符串,使用 strconv.Itoa() 会比 fmt.Sprintf() 要快一倍左右 尽可能地避免把 String 转成[]Byte 。这个转换会导致性能下降。 如果在 for-loop 里对某个 slice 使用 append()请先把 slice 的容量很扩充到位,这样可以避免内存重新分享以及系统自动按 2 的 N 次方幂进行扩展但又用不到,从而浪费内存。 使用 StringBuffer 或是 StringBuild 来拼接字符串,会比使用 + 或 += 性能高三到四个数量级。 尽可能的使用并发的 go routine,然后使用 sync.WaitGroup 来同步分片操作 避免在热代码中进行内存分配,这样会导致 gc 很忙。尽可能的使用 sync.Pool 来重用对象。 使用 lock-free 的操作,避免使用 mutex,尽可能使用 sync/Atomic 包。 (关于无锁编程的相关话题,可参看《无锁队列实现》或《无锁 Hashmap 实现》) 使用 I/O 缓冲,I/O 是个非常非常慢的操作,使用 bufio.NewWrite() 和 bufio.NewReader() 可以带来更高的性能。 对于在 for-loop 里的固定的正则表达式,一定要使用 regexp.Compile() 编译正则表达式。性能会得升两个数量级。 如果你需要更高性能的协议,你要考虑使用 protobuf 或 msgp 而不是 JSON,因为 JSON 的序列化和反序列化里使用了反射。

Hero Image
Go 使用 selenium 截图

Go 使用 selenium 截图 package main import ( "fmt" log "github.com/sirupsen/logrus" "github.com/tebeka/selenium" "io/ioutil" ) const ( chromeDriverPath = "/usr/local/bin/chromedriver" port = 9515 ) func main() { var opts []selenium.ServiceOption selenium.SetDebug(false) service, err := selenium.NewChromeDriverService(chromeDriverPath, port, opts...) if err != nil { panic(err) // panic is used only as an example and is not otherwise recommended. } defer service.Stop() caps := selenium.Capabilities{"browserName": "chrome"} // 整页截屏 // 获取到网页的宽高,然后把浏览器的窗口设置成这么大,然后再截图就好了!需要注意的是只有 headless 模式可以任意设置窗口大小,否则最大高度不能超过你的显示器分辨率 // chromeCaps := chrome.Capabilities{ // Path: "", // Args: []string{ // "--headless", // }, // } // caps.AddChrome(chromeCaps) wd, err := selenium.NewRemote(caps, fmt.Sprintf("http://localhost:%d/wd/hub", port)) if err != nil { panic(err) } defer wd.Quit() // 在 selenium 中使用ExecuteScript就可以执行 JavaScript 代码,需要注意返回值是 interface,所以要类型断言成 float,然后再转成 int // height, _ := wd.ExecuteScript("return document.body.parentNode.scrollHeight", nil) // var realHeight = int(height.(float64)) // 然后我们需要设置窗口大小 // wd.ResizeWindow("", 1920, realHeight) if err := wd.Get("https://github.com/tgbot-collection/archiver"); err != nil { panic(err) } screenshot, err := wd.Screenshot() if err != nil { log.Errorln(err) } ioutil.WriteFile("screenshot.png", screenshot, 0644) } 整页截屏 获取到网页的宽高,然后把浏览器的窗口设置成这么大,然后再截图就好了!需要注意的是只有 headless 模式可以任意设置窗口大小,否则最大高度不能超过你的显示器分辨率

Hero Image
善用 Go Fuzzing,幫助你寫出更完整的單元測試

善用 Go Fuzzing,幫助你寫出更完整的單元測試 func Pow(base uint, exponent uint) uint { if exponent == 0 { return 1 } return base * Pow(base, exponent-1) } func FuzzPow(f *testing.F) { f.Fuzz(func(t *testing.T, x, y uint) { assert := assert.New(t) assert.Equal(Pow(x, 0), uint(1)) assert.Equal(Pow(x, 1), x) assert.Equal(Pow(x, 2), x*x) if x > 0 && y > 0 { assert.Equal(Pow(x, y)/x, Pow(x, y-1)) } }) } go test -fuzz=Fuzz -fuzztime 20s 在做 Fuzzing Test 的時候如果跑一跑 FAIL 了,Go 會幫忙把那組 input 記在 testcase/ 裡面 看了之後會發現在 x=6、y=30 時 assert 會失敗,也就是說 pow(6, 30)/6 不會等於 pow(6, 29)。但這也太奇怪了吧?仔細實驗之後才發現是因為在計算 pow(6, 30) 的時候會發生 overflow。 因為 Go 定義的 max.MaxUint 大約是 18 * 10¹⁸,但 6²⁹ 大概是 7 * 10¹⁸。如果把 6²⁹ 再乘上 6,就會發生 overflow 得到 8 * 10¹⁸,很像繞了操場兩圈結果在跟原本差不多的位置。 var ErrOverflow = fmt.Errorf("overflow") func Pow(base uint, exponent uint) (uint, error) { if exponent == 0 { return 1 } prevResult, err := Pow(base, exponent-1) if math.MaxUint/base < prevResult { return 0, ErrOverflow } return base * prevResult, nil } func FuzzPow(f *testing.F) { f.Fuzz(func(t *testing.T, x, y uint) { assert := assert.New(t) if result, err := Pow(x, 1); err != ErrOverflow { assert.Equal(result, x) } if result, err := Pow(x, y); x > 0 && y > 0 && err != ErrOverflow { resultDivX, _ := Pow(x, y-1) assert.Equal(result/x, resultDivX) } }) }