注意控制频率,因为可能会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
}