github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/compile/internal/gc/timings.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package gc 6 7 import ( 8 "fmt" 9 "io" 10 "strings" 11 "time" 12 ) 13 14 // Timings collects the execution times of labeled phases 15 // which are added trough a sequence of Start/Stop calls. 16 // Events may be associated with each phase via AddEvent. 17 type Timings struct { 18 list []timestamp 19 events map[int][]*event // lazily allocated 20 } 21 22 type timestamp struct { 23 time time.Time 24 label string 25 start bool 26 } 27 28 type event struct { 29 size int64 // count or amount of data processed (allocations, data size, lines, funcs, ...) 30 unit string // unit of size measure (count, MB, lines, funcs, ...) 31 } 32 33 func (t *Timings) append(labels []string, start bool) { 34 t.list = append(t.list, timestamp{time.Now(), strings.Join(labels, ":"), start}) 35 } 36 37 // Start marks the beginning of a new phase and implicitly stops the previous phase. 38 // The phase name is the colon-separated concatenation of the labels. 39 func (t *Timings) Start(labels ...string) { 40 t.append(labels, true) 41 } 42 43 // Stop marks the end of a phase and implicitly starts a new phase. 44 // The labels are added to the labels of the ended phase. 45 func (t *Timings) Stop(labels ...string) { 46 t.append(labels, false) 47 } 48 49 // AddEvent associates an event, i.e., a count, or an amount of data, 50 // with the most recently started or stopped phase; or the very first 51 // phase if Start or Stop hasn't been called yet. The unit specifies 52 // the unit of measurement (e.g., MB, lines, no. of funcs, etc.). 53 func (t *Timings) AddEvent(size int64, unit string) { 54 m := t.events 55 if m == nil { 56 m = make(map[int][]*event) 57 t.events = m 58 } 59 i := len(t.list) 60 if i > 0 { 61 i-- 62 } 63 m[i] = append(m[i], &event{size, unit}) 64 } 65 66 // Write prints the phase times to w. 67 // The prefix is printed at the start of each line. 68 func (t *Timings) Write(w io.Writer, prefix string) { 69 if len(t.list) > 0 { 70 var lines lines 71 72 // group of phases with shared non-empty label prefix 73 var group struct { 74 label string // label prefix 75 tot time.Duration // accumulated phase time 76 size int // number of phases collected in group 77 } 78 79 // accumulated time between Stop/Start timestamps 80 var unaccounted time.Duration 81 82 // process Start/Stop timestamps 83 pt := &t.list[0] // previous timestamp 84 tot := t.list[len(t.list)-1].time.Sub(pt.time) 85 for i := 1; i < len(t.list); i++ { 86 qt := &t.list[i] // current timestamp 87 dt := qt.time.Sub(pt.time) 88 89 var label string 90 var events []*event 91 if pt.start { 92 // previous phase started 93 label = pt.label 94 events = t.events[i-1] 95 if qt.start { 96 // start implicitly ended previous phase; nothing to do 97 } else { 98 // stop ended previous phase; append stop labels, if any 99 if qt.label != "" { 100 label += ":" + qt.label 101 } 102 // events associated with stop replace prior events 103 if e := t.events[i]; e != nil { 104 events = e 105 } 106 } 107 } else { 108 // previous phase stopped 109 if qt.start { 110 // between a stopped and started phase; unaccounted time 111 unaccounted += dt 112 } else { 113 // previous stop implicitly started current phase 114 label = qt.label 115 events = t.events[i] 116 } 117 } 118 if label != "" { 119 // add phase to existing group, or start a new group 120 l := commonPrefix(group.label, label) 121 if group.size == 1 && l != "" || group.size > 1 && l == group.label { 122 // add to existing group 123 group.label = l 124 group.tot += dt 125 group.size++ 126 } else { 127 // start a new group 128 if group.size > 1 { 129 lines.add(prefix+group.label+"subtotal", 1, group.tot, tot, nil) 130 } 131 group.label = label 132 group.tot = dt 133 group.size = 1 134 } 135 136 // write phase 137 lines.add(prefix+label, 1, dt, tot, events) 138 } 139 140 pt = qt 141 } 142 143 if group.size > 1 { 144 lines.add(prefix+group.label+"subtotal", 1, group.tot, tot, nil) 145 } 146 147 if unaccounted != 0 { 148 lines.add(prefix+"unaccounted", 1, unaccounted, tot, nil) 149 } 150 151 lines.add(prefix+"total", 1, tot, tot, nil) 152 153 lines.write(w) 154 } 155 } 156 157 func commonPrefix(a, b string) string { 158 i := 0 159 for i < len(a) && i < len(b) && a[i] == b[i] { 160 i++ 161 } 162 return a[:i] 163 } 164 165 type lines [][]string 166 167 func (lines *lines) add(label string, n int, dt, tot time.Duration, events []*event) { 168 var line []string 169 add := func(format string, args ...interface{}) { 170 line = append(line, fmt.Sprintf(format, args...)) 171 } 172 173 add("%s", label) 174 add(" %d", n) 175 add(" %d ns/op", dt) 176 add(" %.2f %%", float64(dt)/float64(tot)*100) 177 178 for _, e := range events { 179 add(" %d", e.size) 180 add(" %s", e.unit) 181 add(" %d", int64(float64(e.size)/dt.Seconds()+0.5)) 182 add(" %s/s", e.unit) 183 } 184 185 *lines = append(*lines, line) 186 } 187 188 func (lines lines) write(w io.Writer) { 189 // determine column widths and contents 190 var widths []int 191 var number []bool 192 for _, line := range lines { 193 for i, col := range line { 194 if i < len(widths) { 195 if len(col) > widths[i] { 196 widths[i] = len(col) 197 } 198 } else { 199 widths = append(widths, len(col)) 200 number = append(number, isnumber(col)) // first line determines column contents 201 } 202 } 203 } 204 205 // make column widths a multiple of align for more stable output 206 const align = 1 // set to a value > 1 to enable 207 if align > 1 { 208 for i, w := range widths { 209 w += align - 1 210 widths[i] = w - w%align 211 } 212 } 213 214 // print lines taking column widths and contents into account 215 for _, line := range lines { 216 for i, col := range line { 217 format := "%-*s" 218 if number[i] { 219 format = "%*s" // numbers are right-aligned 220 } 221 fmt.Fprintf(w, format, widths[i], col) 222 } 223 fmt.Fprintln(w) 224 } 225 } 226 227 func isnumber(s string) bool { 228 for _, ch := range s { 229 if ch <= ' ' { 230 continue // ignore leading whitespace 231 } 232 return '0' <= ch && ch <= '9' || ch == '.' || ch == '-' || ch == '+' 233 } 234 return false 235 }