github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/dashboard/app/build/perf.go (about)

     1  // Copyright 2014 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  // +build appengine
     6  
     7  package build
     8  
     9  import (
    10  	"fmt"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"appengine"
    16  	"appengine/datastore"
    17  )
    18  
    19  var knownTags = map[string]string{
    20  	"go1":   "0051c7442fed9c888de6617fa9239a913904d96e",
    21  	"go1.1": "d29da2ced72ba2cf48ed6a8f1ec4abc01e4c5bf1",
    22  	"go1.2": "b1edf8faa5d6cbc50c6515785df9df9c19296564",
    23  	"go1.3": "f153208c0a0e306bfca14f71ef11f09859ccabc8",
    24  	"go1.4": "faa3ed1dc30e42771a68b6337dcf8be9518d5c07",
    25  }
    26  
    27  var lastRelease = "go1.4"
    28  
    29  func splitBench(benchProcs string) (string, int) {
    30  	ss := strings.Split(benchProcs, "-")
    31  	procs, _ := strconv.Atoi(ss[1])
    32  	return ss[0], procs
    33  }
    34  
    35  func dashPerfCommits(c appengine.Context, page int) ([]*Commit, error) {
    36  	q := datastore.NewQuery("Commit").
    37  		Ancestor((&Package{}).Key(c)).
    38  		Order("-Num").
    39  		Filter("NeedsBenchmarking =", true).
    40  		Limit(commitsPerPage).
    41  		Offset(page * commitsPerPage)
    42  	var commits []*Commit
    43  	_, err := q.GetAll(c, &commits)
    44  	if err == nil && len(commits) == 0 {
    45  		err = fmt.Errorf("no commits")
    46  	}
    47  	return commits, err
    48  }
    49  
    50  func perfChangeStyle(pc *PerfConfig, v float64, builder, benchmark, metric string) string {
    51  	noise := pc.NoiseLevel(builder, benchmark, metric)
    52  	if isNoise(v, noise) {
    53  		return "noise"
    54  	}
    55  	if v > 0 {
    56  		return "bad"
    57  	}
    58  	return "good"
    59  }
    60  
    61  func isNoise(diff, noise float64) bool {
    62  	rnoise := -100 * noise / (noise + 100)
    63  	return diff < noise && diff > rnoise
    64  }
    65  
    66  func perfDiff(old, new uint64) float64 {
    67  	return 100*float64(new)/float64(old) - 100
    68  }
    69  
    70  func isPerfFailed(res *PerfResult, builder string) bool {
    71  	data := res.ParseData()[builder]
    72  	return data != nil && data["meta-done"] != nil && !data["meta-done"].OK
    73  }
    74  
    75  // PerfResultCache caches a set of PerfResults so that it's easy to access them
    76  // without lots of duplicate accesses to datastore.
    77  // It allows to iterate over newer or older results for some base commit.
    78  type PerfResultCache struct {
    79  	c       appengine.Context
    80  	newer   bool
    81  	iter    *datastore.Iterator
    82  	results map[int]*PerfResult
    83  }
    84  
    85  func MakePerfResultCache(c appengine.Context, com *Commit, newer bool) *PerfResultCache {
    86  	p := &Package{}
    87  	q := datastore.NewQuery("PerfResult").Ancestor(p.Key(c)).Limit(100)
    88  	if newer {
    89  		q = q.Filter("CommitNum >=", com.Num).Order("CommitNum")
    90  	} else {
    91  		q = q.Filter("CommitNum <=", com.Num).Order("-CommitNum")
    92  	}
    93  	rc := &PerfResultCache{c: c, newer: newer, iter: q.Run(c), results: make(map[int]*PerfResult)}
    94  	return rc
    95  }
    96  
    97  func (rc *PerfResultCache) Get(commitNum int) *PerfResult {
    98  	rc.Next(commitNum) // fetch the commit, if necessary
    99  	return rc.results[commitNum]
   100  }
   101  
   102  // Next returns the next PerfResult for the commit commitNum.
   103  // It does not care whether the result has any data, failed or whatever.
   104  func (rc *PerfResultCache) Next(commitNum int) (*PerfResult, error) {
   105  	// See if we have next result in the cache.
   106  	next := -1
   107  	for ci := range rc.results {
   108  		if rc.newer {
   109  			if ci > commitNum && (next == -1 || ci < next) {
   110  				next = ci
   111  			}
   112  		} else {
   113  			if ci < commitNum && (next == -1 || ci > next) {
   114  				next = ci
   115  			}
   116  		}
   117  	}
   118  	if next != -1 {
   119  		return rc.results[next], nil
   120  	}
   121  	// Fetch next result from datastore.
   122  	res := new(PerfResult)
   123  	_, err := rc.iter.Next(res)
   124  	if err == datastore.Done {
   125  		return nil, nil
   126  	}
   127  	if err != nil {
   128  		return nil, fmt.Errorf("fetching perf results: %v", err)
   129  	}
   130  	if (rc.newer && res.CommitNum < commitNum) || (!rc.newer && res.CommitNum > commitNum) {
   131  		rc.c.Errorf("PerfResultCache.Next: bad commit num")
   132  	}
   133  	rc.results[res.CommitNum] = res
   134  	return res, nil
   135  }
   136  
   137  // NextForComparison returns PerfResult which we need to use for performance comprison.
   138  // It skips failed results, but does not skip results with no data.
   139  func (rc *PerfResultCache) NextForComparison(commitNum int, builder string) (*PerfResult, error) {
   140  	for {
   141  		res, err := rc.Next(commitNum)
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  		if res == nil {
   146  			return nil, nil
   147  		}
   148  		if res.CommitNum == commitNum {
   149  			continue
   150  		}
   151  		parsed := res.ParseData()
   152  		if builder != "" {
   153  			// Comparing for a particular builder.
   154  			// This is used in perf_changes and in email notifications.
   155  			b := parsed[builder]
   156  			if b == nil || b["meta-done"] == nil {
   157  				// No results yet, must not do the comparison.
   158  				return nil, nil
   159  			}
   160  			if b["meta-done"].OK {
   161  				// Have complete results, compare.
   162  				return res, nil
   163  			}
   164  		} else {
   165  			// Comparing for all builders, find a result with at least
   166  			// one successful meta-done.
   167  			// This is used in perf_detail.
   168  			for _, benchs := range parsed {
   169  				if data := benchs["meta-done"]; data != nil && data.OK {
   170  					return res, nil
   171  				}
   172  			}
   173  		}
   174  		// Failed, try next result.
   175  		commitNum = res.CommitNum
   176  	}
   177  }
   178  
   179  type PerfChange struct {
   180  	Builder string
   181  	Bench   string
   182  	Metric  string
   183  	Old     uint64
   184  	New     uint64
   185  	Diff    float64
   186  }
   187  
   188  func significantPerfChanges(pc *PerfConfig, builder string, prevRes, res *PerfResult) (changes []*PerfChange) {
   189  	// First, collect all significant changes.
   190  	for builder1, benchmarks1 := range res.ParseData() {
   191  		if builder != "" && builder != builder1 {
   192  			// This is not the builder you're looking for, Luke.
   193  			continue
   194  		}
   195  		benchmarks0 := prevRes.ParseData()[builder1]
   196  		if benchmarks0 == nil {
   197  			continue
   198  		}
   199  		for benchmark, data1 := range benchmarks1 {
   200  			data0 := benchmarks0[benchmark]
   201  			if data0 == nil {
   202  				continue
   203  			}
   204  			for metric, val := range data1.Metrics {
   205  				val0 := data0.Metrics[metric]
   206  				if val0 == 0 {
   207  					continue
   208  				}
   209  				diff := perfDiff(val0, val)
   210  				noise := pc.NoiseLevel(builder, benchmark, metric)
   211  				if isNoise(diff, noise) {
   212  					continue
   213  				}
   214  				ch := &PerfChange{Builder: builder, Bench: benchmark, Metric: metric, Old: val0, New: val, Diff: diff}
   215  				changes = append(changes, ch)
   216  			}
   217  		}
   218  	}
   219  	// Then, strip non-repeatable changes (flakes).
   220  	// The hypothesis is that a real change must show up with the majority of GOMAXPROCS values.
   221  	majority := len(pc.ProcList(builder))/2 + 1
   222  	cnt := make(map[string]int)
   223  	for _, ch := range changes {
   224  		b, _ := splitBench(ch.Bench)
   225  		name := b + "|" + ch.Metric
   226  		if ch.Diff < 0 {
   227  			name += "--"
   228  		}
   229  		cnt[name] = cnt[name] + 1
   230  	}
   231  	for i := 0; i < len(changes); i++ {
   232  		ch := changes[i]
   233  		b, _ := splitBench(ch.Bench)
   234  		name := b + "|" + ch.Metric
   235  		if cnt[name] >= majority {
   236  			continue
   237  		}
   238  		if cnt[name+"--"] >= majority {
   239  			continue
   240  		}
   241  		// Remove flake.
   242  		last := len(changes) - 1
   243  		changes[i] = changes[last]
   244  		changes = changes[:last]
   245  		i--
   246  	}
   247  	return changes
   248  }
   249  
   250  // orderPerfTodo reorders commit nums for benchmarking todo.
   251  // The resulting order is somewhat tricky. We want 2 things:
   252  // 1. benchmark sequentially backwards (this provides information about most
   253  // recent changes, and allows to estimate noise levels)
   254  // 2. benchmark old commits in "scatter" order (this allows to quickly gather
   255  // brief information about thousands of old commits)
   256  // So this function interleaves the two orders.
   257  func orderPerfTodo(nums []int) []int {
   258  	sort.Ints(nums)
   259  	n := len(nums)
   260  	pow2 := uint32(0) // next power-of-two that is >= n
   261  	npow2 := 0
   262  	for npow2 <= n {
   263  		pow2++
   264  		npow2 = 1 << pow2
   265  	}
   266  	res := make([]int, n)
   267  	resPos := n - 1            // result array is filled backwards
   268  	present := make([]bool, n) // denotes values that already present in result array
   269  	for i0, i1 := n-1, 0; i0 >= 0 || i1 < npow2; {
   270  		// i0 represents "benchmark sequentially backwards" sequence
   271  		// find the next commit that is not yet present and add it
   272  		for cnt := 0; cnt < 2; cnt++ {
   273  			for ; i0 >= 0; i0-- {
   274  				if !present[i0] {
   275  					present[i0] = true
   276  					res[resPos] = nums[i0]
   277  					resPos--
   278  					i0--
   279  					break
   280  				}
   281  			}
   282  		}
   283  		// i1 represents "scatter order" sequence
   284  		// find the next commit that is not yet present and add it
   285  		for ; i1 < npow2; i1++ {
   286  			// do the "recursive split-ordering" trick
   287  			idx := 0 // bitwise reverse of i1
   288  			for j := uint32(0); j <= pow2; j++ {
   289  				if (i1 & (1 << j)) != 0 {
   290  					idx = idx | (1 << (pow2 - j - 1))
   291  				}
   292  			}
   293  			if idx < n && !present[idx] {
   294  				present[idx] = true
   295  				res[resPos] = nums[idx]
   296  				resPos--
   297  				i1++
   298  				break
   299  			}
   300  		}
   301  	}
   302  	// The above can't possibly be correct. Do dump check.
   303  	res2 := make([]int, n)
   304  	copy(res2, res)
   305  	sort.Ints(res2)
   306  	for i := range res2 {
   307  		if res2[i] != nums[i] {
   308  			panic(fmt.Sprintf("diff at %v: expect %v, want %v\nwas: %v\n become: %v",
   309  				i, nums[i], res2[i], nums, res2))
   310  		}
   311  	}
   312  	return res
   313  }