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