github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/log_profile.go (about) 1 package libkb 2 3 import ( 4 "bufio" 5 "fmt" 6 "os" 7 "regexp" 8 "strings" 9 "time" 10 11 "golang.org/x/net/context" 12 ) 13 14 // LogProfileContext for LogProfile 15 type LogProfileContext struct { 16 Contextified 17 Path string 18 } 19 20 func (l *LogProfileContext) maxDuration(durations []time.Duration) time.Duration { 21 max := time.Duration(0) 22 for _, d := range durations { 23 if d > max { 24 max = d 25 } 26 } 27 return max 28 } 29 30 func (l *LogProfileContext) minDuration(durations []time.Duration) time.Duration { 31 if len(durations) == 0 { 32 return 0 33 } 34 min := durations[0] 35 for _, d := range durations { 36 if d < min { 37 min = d 38 } 39 } 40 return min 41 } 42 43 func (l *LogProfileContext) avgDuration(durations []time.Duration) time.Duration { 44 if len(durations) == 0 { 45 return 0 46 } 47 var total int64 48 for _, d := range durations { 49 total += d.Nanoseconds() 50 } 51 return time.Duration(total / int64(len(durations))) 52 } 53 54 func (l *LogProfileContext) format(fn string, durations []time.Duration) string { 55 return fmt.Sprintf(` 56 %v: 57 max: %v 58 avg: %v 59 min: %v 60 len: %v`, 61 fn, l.maxDuration(durations), l.avgDuration(durations), l.minDuration(durations), len(durations)) 62 } 63 64 func (l *LogProfileContext) parseMatch(matches []string) (filename, fnName string, d time.Duration) { 65 if len(matches) != 4 { 66 return "", "", 0 67 } 68 filename = matches[1] 69 fnName = matches[2] 70 // Some log calls have fnName: args so we want to strip that. 71 fnName = strings.Split(fnName, ":")[0] 72 // Some log calls have fnName(args) so we want to strip that. 73 fnName = strings.Split(fnName, "(")[0] 74 d, err := time.ParseDuration(matches[3]) 75 if err != nil { 76 l.G().Log.CDebugf(context.TODO(), "Unable to parse duration: %s", err) 77 return "", "", 0 78 } 79 return filename, fnName, d 80 } 81 82 func (l *LogProfileContext) LogProfile(path string) ([]string, error) { 83 f, err := os.Open(path) 84 if err != nil { 85 return nil, err 86 } 87 defer f.Close() 88 89 re := regexp.MustCompile(`keybase (\w*\.go)\:\d+.*- (.*) -> .* \[time=(\d+\.\w+)\]`) 90 // filename -> functionName -> [durations...] 91 profiles := map[string]map[string][]time.Duration{} 92 scanner := bufio.NewScanner(f) 93 scanner.Split(bufio.ScanLines) 94 for scanner.Scan() { 95 // We expect two groups, the function name and a duration 96 matches := re.FindAllStringSubmatch(scanner.Text(), -1) 97 if len(matches) == 0 { 98 continue 99 } 100 filename, fnName, d := l.parseMatch(matches[0]) 101 if fnName == "" { 102 continue 103 } 104 105 data, ok := profiles[filename] 106 if !ok { 107 data = make(map[string][]time.Duration) 108 } 109 durations, ok := data[fnName] 110 if ok { 111 durations = append(durations, d) 112 } else { 113 durations = []time.Duration{d} 114 } 115 data[fnName] = durations 116 profiles[filename] = data 117 } 118 res := []string{} 119 for filename, data := range profiles { 120 res = append(res, filename) 121 for fnName, durations := range data { 122 res = append(res, l.format(fnName, durations)) 123 } 124 } 125 return res, nil 126 }