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 }