1
2
3
4
5
6
7
8
9
10 package benchmark
11
12 import (
13 "fmt"
14 "io"
15 "os"
16 "runtime"
17 "runtime/pprof"
18 "time"
19 "unicode"
20 )
21
22 type Flags int
23
24 const (
25 GC = 1 << iota
26 NoGC Flags = 0
27 )
28
29 type Metrics struct {
30 gc Flags
31 marks []*mark
32 curMark *mark
33 filebase string
34 pprofFile *os.File
35 }
36
37 type mark struct {
38 name string
39 startM, endM, gcM runtime.MemStats
40 startT, endT time.Time
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 func New(gc Flags, filebase string) *Metrics {
72 if gc == GC {
73 runtime.GC()
74 }
75 return &Metrics{gc: gc, filebase: filebase}
76 }
77
78
79
80 func (m *Metrics) Report(w io.Writer) {
81 if m == nil {
82 return
83 }
84
85 m.closeMark()
86
87 gcString := ""
88 if m.gc == GC {
89 gcString = "_GC"
90 }
91
92 var totTime time.Duration
93 for _, curMark := range m.marks {
94 dur := curMark.endT.Sub(curMark.startT)
95 totTime += dur
96 fmt.Fprintf(w, "%s 1 %d ns/op", makeBenchString(curMark.name+gcString), dur.Nanoseconds())
97 fmt.Fprintf(w, "\t%d B/op", curMark.endM.TotalAlloc-curMark.startM.TotalAlloc)
98 fmt.Fprintf(w, "\t%d allocs/op", curMark.endM.Mallocs-curMark.startM.Mallocs)
99 if m.gc == GC {
100 fmt.Fprintf(w, "\t%d live-B", curMark.gcM.HeapAlloc)
101 } else {
102 fmt.Fprintf(w, "\t%d heap-B", curMark.endM.HeapAlloc)
103 }
104 fmt.Fprintf(w, "\n")
105 }
106 fmt.Fprintf(w, "%s 1 %d ns/op\n", makeBenchString("total time"+gcString), totTime.Nanoseconds())
107 }
108
109
110
111 func (m *Metrics) Start(name string) {
112 if m == nil {
113 return
114 }
115 m.closeMark()
116 m.curMark = &mark{name: name}
117
118 if m.shouldPProf() {
119 f, err := os.Create(makePProfFilename(m.filebase, name, "cpuprof"))
120 if err != nil {
121 panic(err)
122 }
123 m.pprofFile = f
124 if err = pprof.StartCPUProfile(m.pprofFile); err != nil {
125 panic(err)
126 }
127 }
128 runtime.ReadMemStats(&m.curMark.startM)
129 m.curMark.startT = time.Now()
130 }
131
132 func (m *Metrics) closeMark() {
133 if m == nil || m.curMark == nil {
134 return
135 }
136 m.curMark.endT = time.Now()
137 if m.shouldPProf() {
138 pprof.StopCPUProfile()
139 m.pprofFile.Close()
140 m.pprofFile = nil
141 }
142 runtime.ReadMemStats(&m.curMark.endM)
143 if m.gc == GC {
144 runtime.GC()
145 runtime.ReadMemStats(&m.curMark.gcM)
146 if m.shouldPProf() {
147
148
149
150
151 runtime.GC()
152 f, err := os.Create(makePProfFilename(m.filebase, m.curMark.name, "memprof"))
153 if err != nil {
154 panic(err)
155 }
156 err = pprof.WriteHeapProfile(f)
157 if err != nil {
158 panic(err)
159 }
160 err = f.Close()
161 if err != nil {
162 panic(err)
163 }
164 }
165 }
166 m.marks = append(m.marks, m.curMark)
167 m.curMark = nil
168 }
169
170
171 func (m *Metrics) shouldPProf() bool {
172 return m != nil && len(m.filebase) > 0
173 }
174
175
176 func makeBenchString(name string) string {
177 needCap := true
178 ret := []rune("Benchmark")
179 for _, r := range name {
180 if unicode.IsSpace(r) {
181 needCap = true
182 continue
183 }
184 if needCap {
185 r = unicode.ToUpper(r)
186 needCap = false
187 }
188 ret = append(ret, r)
189 }
190 return string(ret)
191 }
192
193 func makePProfFilename(filebase, name, typ string) string {
194 return fmt.Sprintf("%s_%s.%s", filebase, makeBenchString(name), typ)
195 }
196
View as plain text