github.com/songzhibin97/gkit@v1.2.13/watching/util.go (about)

     1  package watching
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"math"
     8  	"os"
     9  	"path"
    10  	"runtime"
    11  	"runtime/pprof"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	mem_util "github.com/shirou/gopsutil/mem"
    17  	"github.com/shirou/gopsutil/process"
    18  )
    19  
    20  // copied from https://github.com/containerd/cgroups/blob/318312a373405e5e91134d8063d04d59768a1bff/utils.go#L251
    21  func parseUint(s string, base, bitSize int) (uint64, error) {
    22  	v, err := strconv.ParseUint(s, base, bitSize)
    23  	if err != nil {
    24  		intValue, intErr := strconv.ParseInt(s, base, bitSize)
    25  		// 1. Handle negative values greater than MinInt64 (and)
    26  		// 2. Handle negative values lesser than MinInt64
    27  		if intErr == nil && intValue < 0 {
    28  			return 0, nil
    29  		} else if intErr != nil &&
    30  			intErr.(*strconv.NumError).Err == strconv.ErrRange &&
    31  			intValue < 0 {
    32  			return 0, nil
    33  		}
    34  		return 0, err
    35  	}
    36  	return v, nil
    37  }
    38  
    39  func readUint(path string) (uint64, error) {
    40  	v, err := ioutil.ReadFile(path)
    41  	if err != nil {
    42  		return 0, err
    43  	}
    44  	return parseUint(strings.TrimSpace(string(v)), 10, 64)
    45  }
    46  
    47  // only reserve the top n.
    48  func trimResult(buffer bytes.Buffer) string {
    49  	index := TrimResultTopN
    50  	arr := strings.SplitN(buffer.String(), "\n\n", TrimResultTopN+1)
    51  
    52  	if len(arr) <= TrimResultTopN {
    53  		index = len(arr) - 1
    54  	}
    55  
    56  	return strings.Join(arr[:index], "\n\n")
    57  }
    58  
    59  // return values:
    60  // 1. cpu percent, not division cpu cores yet,
    61  // 2. RSS mem in bytes,
    62  // 3. goroutine num,
    63  // 4. thread num
    64  func getUsage() (float64, uint64, int, int, error) {
    65  	p, err := process.NewProcess(int32(os.Getpid()))
    66  	if err != nil {
    67  		return 0, 0, 0, 0, err
    68  	}
    69  	cpuPercent, err := p.Percent(time.Second)
    70  	if err != nil {
    71  		return 0, 0, 0, 0, err
    72  	}
    73  
    74  	mem, err := p.MemoryInfo()
    75  	if err != nil {
    76  		return 0, 0, 0, 0, err
    77  	}
    78  
    79  	rss := mem.RSS
    80  	gNum := runtime.NumGoroutine()
    81  	tNum := getThreadNum()
    82  
    83  	return cpuPercent, rss, gNum, tNum, nil
    84  }
    85  
    86  // get cpu core number limited by CGroup.
    87  func getCGroupCPUCore() (float64, error) {
    88  	var cpuQuota uint64
    89  
    90  	cpuPeriod, err := readUint(cgroupCpuPeriodPath)
    91  	if cpuPeriod == 0 || err != nil {
    92  		return 0, err
    93  	}
    94  
    95  	if cpuQuota, err = readUint(cgroupCpuQuotaPath); err != nil {
    96  		return 0, err
    97  	}
    98  
    99  	return float64(cpuQuota) / float64(cpuPeriod), nil
   100  }
   101  
   102  func getCGroupMemoryLimit() (uint64, error) {
   103  	usage, err := readUint(cgroupMemLimitPath)
   104  	if err != nil {
   105  		return 0, err
   106  	}
   107  	machineMemory, err := mem_util.VirtualMemory()
   108  	if err != nil {
   109  		return 0, err
   110  	}
   111  	limit := uint64(math.Min(float64(usage), float64(machineMemory.Total)))
   112  	return limit, nil
   113  }
   114  
   115  func getNormalMemoryLimit() (uint64, error) {
   116  	machineMemory, err := mem_util.VirtualMemory()
   117  	if err != nil {
   118  		return 0, err
   119  	}
   120  	return machineMemory.Total, nil
   121  }
   122  
   123  func getThreadNum() int {
   124  	return pprof.Lookup("threadcreate").Count()
   125  }
   126  
   127  // cpu mem goroutine thread err.
   128  func collect(cpuCore float64, memoryLimit uint64) (int, int, int, int, error) {
   129  	cpu, mem, gNum, tNum, err := getUsage()
   130  	if err != nil {
   131  		return 0, 0, 0, 0, err
   132  	}
   133  
   134  	// The default percent is from all cores, multiply by cpu core
   135  	// but it's inconvenient to calculate the proper percent
   136  	// here we divide by core number, so we can set a percent bar more intuitively
   137  	cpuPercent := cpu / cpuCore
   138  
   139  	memPercent := float64(mem) / float64(memoryLimit) * 100
   140  
   141  	return int(cpuPercent), int(memPercent), gNum, tNum, nil
   142  }
   143  
   144  func matchRule(history ring, curVal, ruleMin, ruleAbs, ruleDiff, ruleMax int) (bool, string) {
   145  	// should bigger than rule min
   146  	if curVal < ruleMin {
   147  		return false, fmt.Sprintf("curVal [%d]< ruleMin [%d]", curVal, ruleMin)
   148  	}
   149  
   150  	// if ruleMax is enable and current value bigger max, skip dumping
   151  	if ruleMax != NotSupportTypeMaxConfig && curVal >= ruleMax {
   152  		return false, ""
   153  	}
   154  
   155  	// the current peak load exceed the absolute value
   156  	if curVal > ruleAbs {
   157  		return true, fmt.Sprintf("curVal [%d] > ruleAbs [%d]", curVal, ruleAbs)
   158  	}
   159  
   160  	// the peak load matches the rule
   161  	avg := history.avg()
   162  	if curVal >= avg*(100+ruleDiff)/100 {
   163  		return true, fmt.Sprintf("curVal[%d] >= avg[%d]*(100+ruleDiff)/100", curVal, avg)
   164  	}
   165  	return false, ""
   166  }
   167  
   168  func getBinaryFileName(filePath string, dumpType configureType, eventID string) string {
   169  	binarySuffix := time.Now().Format("20060102150405.000") + ".bin"
   170  
   171  	return path.Join(filePath, type2name[dumpType]+"."+eventID+"."+binarySuffix)
   172  }
   173  
   174  // getBinaryFileNameAndCreate 获取文件路径并创建
   175  func getBinaryFileNameAndCreate(dump string, dumpType configureType, eventID string) (*os.File, string, error) {
   176  	filepath := getBinaryFileName(dump, dumpType, eventID)
   177  	f, err := os.OpenFile(filepath, defaultLoggerFlags, defaultLoggerPerm)
   178  	if err != nil && os.IsNotExist(err) {
   179  		if err = os.MkdirAll(dump, 0o755); err != nil {
   180  			return nil, filepath, err
   181  		}
   182  		f, err = os.OpenFile(filepath, defaultLoggerFlags, defaultLoggerPerm)
   183  		if err != nil {
   184  			return nil, filepath, err
   185  		}
   186  	}
   187  	return f, filepath, err
   188  }
   189  
   190  func writeFile(data bytes.Buffer, dumpType configureType, dumpConfigs *DumpConfigs, eventID string) error {
   191  	if dumpConfigs.DumpProfileType == textDump {
   192  		// write to log
   193  		if dumpConfigs.DumpFullStack {
   194  			res := trimResult(data)
   195  			return fmt.Errorf(res) // nolint:goerr113
   196  		}
   197  		return fmt.Errorf(data.String())
   198  	}
   199  
   200  	bf, _, err := getBinaryFileNameAndCreate(dumpConfigs.DumpPath, dumpType, eventID) // nolint:gosec
   201  	if err != nil {
   202  		return fmt.Errorf("[Watching] pprof %v write to file failed : %w", type2name[dumpType], err)
   203  	}
   204  	defer bf.Close() //nolint:errcheck,gosec
   205  
   206  	if _, err = bf.Write(data.Bytes()); err != nil {
   207  		return fmt.Errorf("[Watching] pprof %v write to file failed : %w", type2name[dumpType], err)
   208  	}
   209  	return nil
   210  }