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  }