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 }