1
// Copyright 2022 The CubeFS Authors.
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
7
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
// implied. See the License for the specific language governing
13
// permissions and limitations under the License.
32
"github.com/cubefs/cubefs/util/log"
36
Stat_Module = "mem_stat"
37
FileNameDateFormat = "20060102150405"
38
ShiftedExtension = ".old"
39
PRO_MEM = "/proc/%d/status"
43
DefaultStatLogSize = 200 * 1024 * 1024 // 200M
44
DefaultHeadRoom = 50 * 1024 // 50G
45
MaxReservedDays = 7 * 24 * time.Hour
48
var DefaultTimeOutUs = [MaxTimeoutLevel]uint32{100000, 500000, 1000000}
50
var DefaultStatInterval = 60 * time.Second // 60 seconds
52
var re = regexp.MustCompile(`\([0-9]*\)`)
54
type ShiftedFile []os.FileInfo
56
func (f ShiftedFile) Less(i, j int) bool {
57
return f[i].ModTime().Before(f[j].ModTime())
60
func (f ShiftedFile) Len() int {
64
func (f ShiftedFile) Swap(i, j int) {
65
f[i], f[j] = f[j], f[i]
74
allTimeUs time.Duration
75
timeOut [MaxTimeoutLevel]uint32
78
type Statistic struct {
83
lastClearTime time.Time
84
timeOutUs [MaxTimeoutLevel]uint32
85
typeInfoMap map[string]*typeInfo
91
var gSt *Statistic = nil
93
func NewStatistic(dir, logModule string, logMaxSize int64, timeOutUs [MaxTimeoutLevel]uint32, useMutex bool) (*Statistic, error) {
94
dir = path.Join(dir, logModule)
95
fi, err := os.Stat(dir)
97
os.MkdirAll(dir, 0o755)
100
return nil, errors.New(dir + " is not a directory")
103
_ = os.Chmod(dir, 0o755)
104
logName := path.Join(dir, Stat_Module)
107
logMaxSize: logMaxSize,
108
logBaseName: logName,
110
lastClearTime: time.Time{},
111
timeOutUs: timeOutUs,
112
typeInfoMap: make(map[string]*typeInfo),
119
go st.flushScheduler()
123
// TODO: how to close?
124
func (st *Statistic) flushScheduler() {
125
timer := time.NewTimer(DefaultStatInterval)
132
log.LogErrorf("WriteStat error: %v", err)
135
timer.Reset(DefaultStatInterval)
137
fs := syscall.Statfs_t{}
138
if err := syscall.Statfs(st.logDir, &fs); err != nil {
139
log.LogErrorf("Get fs stat failed, err: %v", err)
142
diskSpaceLeft := int64(fs.Bavail * uint64(fs.Bsize))
143
diskSpaceLeft -= DefaultHeadRoom * 1024 * 1024
144
removeLogFile(diskSpaceLeft, Stat_Module)
148
func removeLogFile(diskSpaceLeft int64, module string) {
149
fInfos, err := ioutil.ReadDir(gSt.logDir)
151
log.LogErrorf("ReadDir failed, logDir: %s, err: %v", gSt.logDir, err)
154
var needDelFiles ShiftedFile
155
for _, info := range fInfos {
156
if deleteFileFilter(info, diskSpaceLeft, module) {
157
needDelFiles = append(needDelFiles, info)
160
sort.Sort(needDelFiles)
161
for _, info := range needDelFiles {
162
if err = os.Remove(path.Join(gSt.logDir, info.Name())); err != nil {
163
log.LogErrorf("Remove log file failed, logFileName: %s, err: %v", info.Name(), err)
166
diskSpaceLeft += info.Size()
167
if diskSpaceLeft > 0 && time.Since(info.ModTime()) < MaxReservedDays {
173
func deleteFileFilter(info os.FileInfo, diskSpaceLeft int64, module string) bool {
174
if diskSpaceLeft <= 0 {
175
return info.Mode().IsRegular() && strings.HasSuffix(info.Name(), ShiftedExtension) && strings.HasPrefix(info.Name(), module)
177
return time.Since(info.ModTime()) > MaxReservedDays && strings.HasSuffix(info.Name(), ShiftedExtension) && strings.HasPrefix(info.Name(), module)
188
func BeginStat() (bgTime *time.Time) {
193
func EndStat(typeName string, err error, bgTime *time.Time, statCount uint32) error {
208
newErrStr := string(re.ReplaceAll([]byte(err.Error()), []byte("(xxx)")))
209
baseLen := len(typeName) + 2
210
if len(newErrStr)+baseLen > 41 {
211
typeName = typeName + "[" + newErrStr[:41-baseLen] + "]"
213
typeName = typeName + "[" + newErrStr + "]"
217
return addStat(typeName, err, bgTime, statCount)
220
func WriteStat() error {
230
logFileName := gSt.logBaseName + ".log"
231
statFile, err := os.OpenFile(logFileName, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0o666)
233
log.LogErrorf("OpenLogFile failed, logFileName: %s, err: %v\n", logFileName, err)
234
return fmt.Errorf("OpenLogFile failed, logFileName %s\n", logFileName)
236
defer statFile.Close()
238
statSpan := time.Since(gSt.lastClearTime) / 1e9
239
ioStream := bufio.NewWriter(statFile)
240
defer ioStream.Flush()
242
fmt.Fprintf(ioStream, "\n=============== Statistic in %ds, %s =====================\n",
243
statSpan, time.Now().Format("2006-01-02 15:04:05"))
245
if virt, res, err := GetProcessMemory(gSt.pid); err != nil {
246
log.LogErrorf("Get process memory failed, err: %v", err)
247
fmt.Fprintf(ioStream, "Get Mem Failed.\n")
249
fmt.Fprintf(ioStream, "Mem Allocated(kB): VIRT %-10d RES %-10d\n", virt, res)
252
fmt.Fprintf(ioStream, "%-42s|%10s|%8s|%8s|%8s|%8s|%8s|%8s|%8s|\n",
253
"", "TOTAL", "FAILED", "AVG(ms)", "MAX(ms)", "MIN(ms)",
254
">"+strconv.Itoa(int(gSt.timeOutUs[0])/1000)+"ms",
255
">"+strconv.Itoa(int(gSt.timeOutUs[1])/1000)+"ms",
256
">"+strconv.Itoa(int(gSt.timeOutUs[2])/1000)+"ms")
258
typeNames := make([]string, 0)
259
for typeName := range gSt.typeInfoMap {
260
typeNames = append(typeNames, typeName)
263
sort.Strings(typeNames)
264
for _, typeName := range typeNames {
265
typeInfo := gSt.typeInfoMap[typeName]
267
if typeInfo.allCount > 0 {
268
avgUs = int32(typeInfo.allTimeUs / time.Duration(typeInfo.allCount))
271
fmt.Fprintf(ioStream, "%-42s|%10d|%8d|%8.2f|%8.2f|%8.2f|%8d|%8d|%8d|\n",
272
typeInfo.typeName, typeInfo.allCount, typeInfo.failCount,
273
float32(avgUs)/1000, float32(typeInfo.maxTime)/1000, float32(typeInfo.minTime)/1000,
274
typeInfo.timeOut[0], typeInfo.timeOut[1], typeInfo.timeOut[2])
277
fmt.Fprintf(ioStream, "-------------------------------------------------------------------"+
278
"--------------------------------------------------\n")
281
gSt.lastClearTime = time.Now()
282
gSt.typeInfoMap = make(map[string]*typeInfo)
299
gSt.lastClearTime = time.Now()
300
gSt.typeInfoMap = make(map[string]*typeInfo)
303
func addStat(typeName string, err error, bgTime *time.Time, statCount uint32) error {
308
if len(typeName) == 0 {
309
return fmt.Errorf("AddStat fail, typeName %s\n", typeName)
312
if typeInfo, ok := gSt.typeInfoMap[typeName]; ok {
313
typeInfo.allCount += statCount
315
typeInfo.failCount += statCount
317
addTime(typeInfo, bgTime)
321
typeInfo := &typeInfo{
328
timeOut: [3]uint32{},
332
typeInfo.failCount = statCount
335
gSt.typeInfoMap[typeName] = typeInfo
336
addTime(typeInfo, bgTime)
341
func addTime(typeInfo *typeInfo, bgTime *time.Time) {
346
timeCostUs := time.Since(*bgTime) / 1e3
351
if timeCostUs >= time.Duration(gSt.timeOutUs[0]) && timeCostUs < time.Duration(gSt.timeOutUs[1]) {
352
typeInfo.timeOut[0]++
353
} else if timeCostUs >= time.Duration(gSt.timeOutUs[1]) && timeCostUs < time.Duration(gSt.timeOutUs[2]) {
354
typeInfo.timeOut[1]++
355
} else if timeCostUs > time.Duration(gSt.timeOutUs[2]) {
356
typeInfo.timeOut[2]++
359
if timeCostUs > typeInfo.maxTime {
360
typeInfo.maxTime = timeCostUs
362
if typeInfo.minTime == 0 || timeCostUs < typeInfo.minTime {
363
typeInfo.minTime = timeCostUs
366
typeInfo.allTimeUs += timeCostUs
369
func shiftFiles() error {
370
logFileName := gSt.logBaseName + ".log"
371
fileInfo, err := os.Stat(logFileName)
376
if fileInfo.Size() < gSt.logMaxSize {
380
if syscall.Access(logFileName, F_OK) == nil {
381
logNewFileName := logFileName + "." + time.Now().Format(
382
FileNameDateFormat) + ShiftedExtension
383
if syscall.Rename(logFileName, logNewFileName) != nil {
384
log.LogErrorf("RenameFile failed, logFileName: %s, logNewFileName: %s, err: %v\n",
385
logFileName, logNewFileName, err)
386
return fmt.Errorf("RenameFile failed, logFileName %s, logNewFileName %s\n",
387
logFileName, logNewFileName)
394
func StatBandWidth(typeName string, Size uint32) {
395
EndStat(typeName+"[FLOW_KB]", nil, nil, Size/1024)
398
func GetMememory() (Virt, Res uint64, err error) {
399
return GetProcessMemory(gSt.pid)
402
func GetProcessMemory(pid int) (Virt, Res uint64, err error) {
403
proFileName := fmt.Sprintf(PRO_MEM, pid)
404
fp, err := os.Open(proFileName)
409
scan := bufio.NewScanner(fp)
412
fields := strings.Split(line, ":")
415
value := strings.TrimSpace(fields[1])
416
value = strings.Replace(value, " kB", "", -1)
417
Res, err = strconv.ParseUint(value, 10, 64)
421
} else if key == "VmSize" {
422
value := strings.TrimSpace(fields[1])
423
value = strings.Replace(value, " kB", "", -1)
424
Virt, err = strconv.ParseUint(value, 10, 64)