注意控制频率,因为可能会STW
问题背景
平时查看问题,可以通过go tool进行查看,内存,函数栈,CPU等信息,但是如果需要定时获取内存信息,查看内存占用排名较高的函数是不是某个特定的函数,就需要调研go tool的高阶使用方案。
梳理调研
经过调研 pprof可以通过添加参数获取不同的指标,详见https://github.com/google/pprof/blob/master/doc/README.md
如下两个例子,第一个获取文本格式的数据,以cum排序,显示10条数据,也就是获取cum排序前十的结果;第二个例子还添加了筛选指定字段的功能。
例1:go tool pprof -text -cum -nodecount=10 http://IP:port/debug/pprof/heap
例2:go tool pprof -text -cum -show=BufferReader -nodecount=10 http://IP:port/debug/pprof/heap
同时,这个功能如果用代码自动化实现,这里可以通过go exec执行命令,即可获取对应的结果值,之后对结果进行各种处理
编码实战
package tools
import (
"bytes"
"fmt"
"os/exec"
"strconv"
"strings"
)
const (
_ = iota
KB = 1 << (10 * iota)
MB
GB
TB
PB
)
const (
KBStr = "KB"
MBStr = "MB"
GBStr = "GB"
TBStr = "TB"
PBStr = "PB"
)
func CheckMem(nodeAddr, pprofPort, targetStr string) (resultMap map[string]float64) {
resultMap = make(map[string]float64)
// 构造pprof端口 执行
split := strings.Split(nodeAddr, ":")
pprofUrl := fmt.Sprintf("http://%s:%s/debug/pprof/heap", split[0], pprofPort)
//go tool pprof -text -cum -nodecount=5 pprofUrl
cmd := exec.Command("go", "tool", "pprof", "-text", "-cum", "-nodecount=5", pprofUrl)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout // 标准输出
cmd.Stderr = &stderr // 标准错误
err := cmd.Run()
if err != nil {
fmt.Println(err)
return
}
outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
fmt.Println(errStr)
//outStr:
//File: xxx
//Build ID: 6d927e53218a4cd38bac2c056bc2f20xxx
//Type: inuse_space
//Time: Nov 2, 2021 at 1:39am (CST)
//Showing nodes accounting for 0, 0% of 75.83MB total
//Showing top 5 nodes out of 93
// flat flat% sum% cum cum%
// 0 0% 0% 68.71MB 90.61% runtime.main
// 0 0% 0% 57.65MB 76.03% main.main
// 0 0% 0% 57.65MB 76.03% main.run
// 0 0% 0% 56.02MB 73.87% github.com/xx/xxx/util/log.(*Log).initLog
// 0 0% 0% 56.02MB 73.87% github.com/xx/xxx/util/log.(*Log).initLog.func1
//
//errStr:
//Fetching profile over HTTP from http://0.0.0.0:49185/debug/pprof/heap
//Saved profile in /root/pprof/pprof.xxx.alloc_objects.alloc_space.inuse_objects.inuse_space.003.pb.gz
//对outStr进行具体分析
//读取文件的每一行 判断是否有 targetStr
//对这一行进行分隔 获取到 cum对应的值 分析这个字符串代表的大小 注意单位 KB MB GB TB
lines := strings.Split(outStr, "\n")
for _, line := range lines {
if !strings.Contains(line, targetStr) {
continue
}
details := strings.Split(line, " ")
index := 0
var memUse float64
for _, detail := range details {
if detail != "" {
index++
}
if index == 4 {
memUse, err = convertStrToMemUse(detail)
break
}
}
if err != nil {
continue
}
resultMap[line] = memUse
}
return
}
func convertStrToMemUse(str string) (float64, error) {
var (
unit float64
valueStr string
)
if strings.HasSuffix(str, KBStr) {
unit = KB
valueStr = strings.Trim(str, KBStr)
} else if strings.HasSuffix(str, MBStr) {
unit = MB
valueStr = strings.Trim(str, MBStr)
} else if strings.HasSuffix(str, GBStr) {
unit = GB
valueStr = strings.Trim(str, GBStr)
} else if strings.HasSuffix(str, TBStr) {
unit = TB
valueStr = strings.Trim(str, TBStr)
} else if strings.HasSuffix(str, PBStr) {
unit = PB
valueStr = strings.Trim(str, PBStr)
}
valueFloat, err := strconv.ParseFloat(valueStr, 64)
if err != nil {
return 0, err
}
return valueFloat * unit, nil
}