1
2
3
4
5 package base
6
7 import (
8 "fmt"
9 "io"
10 "strings"
11 "time"
12 )
13
14 var Timer Timings
15
16
17
18
19 type Timings struct {
20 list []timestamp
21 events map[int][]*event
22 }
23
24 type timestamp struct {
25 time time.Time
26 label string
27 start bool
28 }
29
30 type event struct {
31 size int64
32 unit string
33 }
34
35 func (t *Timings) append(labels []string, start bool) {
36 t.list = append(t.list, timestamp{time.Now(), strings.Join(labels, ":"), start})
37 }
38
39
40
41 func (t *Timings) Start(labels ...string) {
42 t.append(labels, true)
43 }
44
45
46
47 func (t *Timings) Stop(labels ...string) {
48 t.append(labels, false)
49 }
50
51
52
53
54
55 func (t *Timings) AddEvent(size int64, unit string) {
56 m := t.events
57 if m == nil {
58 m = make(map[int][]*event)
59 t.events = m
60 }
61 i := len(t.list)
62 if i > 0 {
63 i--
64 }
65 m[i] = append(m[i], &event{size, unit})
66 }
67
68
69
70 func (t *Timings) Write(w io.Writer, prefix string) {
71 if len(t.list) > 0 {
72 var lines lines
73
74
75 var group struct {
76 label string
77 tot time.Duration
78 size int
79 }
80
81
82 var unaccounted time.Duration
83
84
85 pt := &t.list[0]
86 tot := t.list[len(t.list)-1].time.Sub(pt.time)
87 for i := 1; i < len(t.list); i++ {
88 qt := &t.list[i]
89 dt := qt.time.Sub(pt.time)
90
91 var label string
92 var events []*event
93 if pt.start {
94
95 label = pt.label
96 events = t.events[i-1]
97 if qt.start {
98
99 } else {
100
101 if qt.label != "" {
102 label += ":" + qt.label
103 }
104
105 if e := t.events[i]; e != nil {
106 events = e
107 }
108 }
109 } else {
110
111 if qt.start {
112
113 unaccounted += dt
114 } else {
115
116 label = qt.label
117 events = t.events[i]
118 }
119 }
120 if label != "" {
121
122 l := commonPrefix(group.label, label)
123 if group.size == 1 && l != "" || group.size > 1 && l == group.label {
124
125 group.label = l
126 group.tot += dt
127 group.size++
128 } else {
129
130 if group.size > 1 {
131 lines.add(prefix+group.label+"subtotal", 1, group.tot, tot, nil)
132 }
133 group.label = label
134 group.tot = dt
135 group.size = 1
136 }
137
138
139 lines.add(prefix+label, 1, dt, tot, events)
140 }
141
142 pt = qt
143 }
144
145 if group.size > 1 {
146 lines.add(prefix+group.label+"subtotal", 1, group.tot, tot, nil)
147 }
148
149 if unaccounted != 0 {
150 lines.add(prefix+"unaccounted", 1, unaccounted, tot, nil)
151 }
152
153 lines.add(prefix+"total", 1, tot, tot, nil)
154
155 lines.write(w)
156 }
157 }
158
159 func commonPrefix(a, b string) string {
160 i := 0
161 for i < len(a) && i < len(b) && a[i] == b[i] {
162 i++
163 }
164 return a[:i]
165 }
166
167 type lines [][]string
168
169 func (lines *lines) add(label string, n int, dt, tot time.Duration, events []*event) {
170 var line []string
171 add := func(format string, args ...interface{}) {
172 line = append(line, fmt.Sprintf(format, args...))
173 }
174
175 add("%s", label)
176 add(" %d", n)
177 add(" %d ns/op", dt)
178 add(" %.2f %%", float64(dt)/float64(tot)*100)
179
180 for _, e := range events {
181 add(" %d", e.size)
182 add(" %s", e.unit)
183 add(" %d", int64(float64(e.size)/dt.Seconds()+0.5))
184 add(" %s/s", e.unit)
185 }
186
187 *lines = append(*lines, line)
188 }
189
190 func (lines lines) write(w io.Writer) {
191
192 var widths []int
193 var number []bool
194 for _, line := range lines {
195 for i, col := range line {
196 if i < len(widths) {
197 if len(col) > widths[i] {
198 widths[i] = len(col)
199 }
200 } else {
201 widths = append(widths, len(col))
202 number = append(number, isnumber(col))
203 }
204 }
205 }
206
207
208 const align = 1
209 if align > 1 {
210 for i, w := range widths {
211 w += align - 1
212 widths[i] = w - w%align
213 }
214 }
215
216
217 for _, line := range lines {
218 for i, col := range line {
219 format := "%-*s"
220 if number[i] {
221 format = "%*s"
222 }
223 fmt.Fprintf(w, format, widths[i], col)
224 }
225 fmt.Fprintln(w)
226 }
227 }
228
229 func isnumber(s string) bool {
230 for _, ch := range s {
231 if ch <= ' ' {
232 continue
233 }
234 return '0' <= ch && ch <= '9' || ch == '.' || ch == '-' || ch == '+'
235 }
236 return false
237 }
238
View as plain text