github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/dashboard/app/build/build.go (about) 1 // Copyright 2011 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 "bytes" 11 "compress/gzip" 12 "crypto/sha1" 13 "errors" 14 "fmt" 15 "io" 16 "io/ioutil" 17 "net/http" 18 "sort" 19 "strconv" 20 "strings" 21 "time" 22 23 "appengine" 24 "appengine/datastore" 25 26 "cache" 27 ) 28 29 const ( 30 maxDatastoreStringLen = 500 31 PerfRunLength = 1024 32 ) 33 34 // A Package describes a package that is listed on the dashboard. 35 type Package struct { 36 Kind string // "subrepo", "external", or empty for the main Go tree 37 Name string 38 Path string // (empty for the main Go tree) 39 NextNum int // Num of the next head Commit 40 } 41 42 func (p *Package) String() string { 43 return fmt.Sprintf("%s: %q", p.Path, p.Name) 44 } 45 46 func (p *Package) Key(c appengine.Context) *datastore.Key { 47 key := p.Path 48 if key == "" { 49 key = "go" 50 } 51 return datastore.NewKey(c, "Package", key, 0, nil) 52 } 53 54 // LastCommit returns the most recent Commit for this Package. 55 func (p *Package) LastCommit(c appengine.Context) (*Commit, error) { 56 var commits []*Commit 57 _, err := datastore.NewQuery("Commit"). 58 Ancestor(p.Key(c)). 59 Order("-Time"). 60 Limit(1). 61 GetAll(c, &commits) 62 if err != nil { 63 return nil, err 64 } 65 if len(commits) != 1 { 66 return nil, datastore.ErrNoSuchEntity 67 } 68 return commits[0], nil 69 } 70 71 // GetPackage fetches a Package by path from the datastore. 72 func GetPackage(c appengine.Context, path string) (*Package, error) { 73 p := &Package{Path: path} 74 err := datastore.Get(c, p.Key(c), p) 75 if err == datastore.ErrNoSuchEntity { 76 return nil, fmt.Errorf("package %q not found", path) 77 } 78 return p, err 79 } 80 81 // A Commit describes an individual commit in a package. 82 // 83 // Each Commit entity is a descendant of its associated Package entity. 84 // In other words, all Commits with the same PackagePath belong to the same 85 // datastore entity group. 86 type Commit struct { 87 PackagePath string // (empty for main repo commits) 88 Hash string 89 ParentHash string 90 Num int // Internal monotonic counter unique to this package. 91 92 User string 93 Desc string `datastore:",noindex"` 94 Time time.Time 95 NeedsBenchmarking bool 96 TryPatch bool 97 Branch string 98 99 // ResultData is the Data string of each build Result for this Commit. 100 // For non-Go commits, only the Results for the current Go tip, weekly, 101 // and release Tags are stored here. This is purely de-normalized data. 102 // The complete data set is stored in Result entities. 103 ResultData []string `datastore:",noindex"` 104 105 // PerfResults holds a set of “builder|benchmark” tuples denoting 106 // what benchmarks have been executed on the commit. 107 PerfResults []string `datastore:",noindex"` 108 109 FailNotificationSent bool 110 } 111 112 func (com *Commit) Key(c appengine.Context) *datastore.Key { 113 if com.Hash == "" { 114 panic("tried Key on Commit with empty Hash") 115 } 116 p := Package{Path: com.PackagePath} 117 key := com.PackagePath + "|" + com.Hash 118 return datastore.NewKey(c, "Commit", key, 0, p.Key(c)) 119 } 120 121 func (c *Commit) Valid() error { 122 if !validHash(c.Hash) { 123 return errors.New("invalid Hash") 124 } 125 if c.ParentHash != "" && !validHash(c.ParentHash) { // empty is OK 126 return errors.New("invalid ParentHash") 127 } 128 return nil 129 } 130 131 func putCommit(c appengine.Context, com *Commit) error { 132 if err := com.Valid(); err != nil { 133 return fmt.Errorf("putting Commit: %v", err) 134 } 135 if com.Num == 0 && com.ParentHash != "0000" { // 0000 is used in tests 136 return fmt.Errorf("putting Commit: invalid Num (must be > 0)") 137 } 138 if _, err := datastore.Put(c, com.Key(c), com); err != nil { 139 return fmt.Errorf("putting Commit: %v", err) 140 } 141 return nil 142 } 143 144 // each result line is approx 105 bytes. This constant is a tradeoff between 145 // build history and the AppEngine datastore limit of 1mb. 146 const maxResults = 1000 147 148 // AddResult adds the denormalized Result data to the Commit's Result field. 149 // It must be called from inside a datastore transaction. 150 func (com *Commit) AddResult(c appengine.Context, r *Result) error { 151 if err := datastore.Get(c, com.Key(c), com); err != nil { 152 return fmt.Errorf("getting Commit: %v", err) 153 } 154 155 var resultExists bool 156 for i, s := range com.ResultData { 157 // if there already exists result data for this builder at com, overwrite it. 158 if strings.HasPrefix(s, r.Builder+"|") && strings.HasSuffix(s, "|"+r.GoHash) { 159 resultExists = true 160 com.ResultData[i] = r.Data() 161 } 162 } 163 if !resultExists { 164 // otherwise, add the new result data for this builder. 165 com.ResultData = trim(append(com.ResultData, r.Data()), maxResults) 166 } 167 return putCommit(c, com) 168 } 169 170 // AddPerfResult remembers that the builder has run the benchmark on the commit. 171 // It must be called from inside a datastore transaction. 172 func (com *Commit) AddPerfResult(c appengine.Context, builder, benchmark string) error { 173 if err := datastore.Get(c, com.Key(c), com); err != nil { 174 return fmt.Errorf("getting Commit: %v", err) 175 } 176 if !com.NeedsBenchmarking { 177 return fmt.Errorf("trying to add perf result to Commit(%v) that does not require benchmarking", com.Hash) 178 } 179 s := builder + "|" + benchmark 180 for _, v := range com.PerfResults { 181 if v == s { 182 return nil 183 } 184 } 185 com.PerfResults = append(com.PerfResults, s) 186 return putCommit(c, com) 187 } 188 189 func trim(s []string, n int) []string { 190 l := min(len(s), n) 191 return s[len(s)-l:] 192 } 193 194 func min(a, b int) int { 195 if a < b { 196 return a 197 } 198 return b 199 } 200 201 // Result returns the build Result for this Commit for the given builder/goHash. 202 func (c *Commit) Result(builder, goHash string) *Result { 203 for _, r := range c.ResultData { 204 p := strings.SplitN(r, "|", 4) 205 if len(p) != 4 || p[0] != builder || p[3] != goHash { 206 continue 207 } 208 return partsToHash(c, p) 209 } 210 return nil 211 } 212 213 // Results returns the build Results for this Commit. 214 func (c *Commit) Results() (results []*Result) { 215 for _, r := range c.ResultData { 216 p := strings.SplitN(r, "|", 4) 217 if len(p) != 4 { 218 continue 219 } 220 results = append(results, partsToHash(c, p)) 221 } 222 return 223 } 224 225 func (c *Commit) ResultGoHashes() []string { 226 // For the main repo, just return the empty string 227 // (there's no corresponding main repo hash for a main repo Commit). 228 // This function is only really useful for sub-repos. 229 if c.PackagePath == "" { 230 return []string{""} 231 } 232 var hashes []string 233 for _, r := range c.ResultData { 234 p := strings.SplitN(r, "|", 4) 235 if len(p) != 4 { 236 continue 237 } 238 // Append only new results (use linear scan to preserve order). 239 if !contains(hashes, p[3]) { 240 hashes = append(hashes, p[3]) 241 } 242 } 243 // Return results in reverse order (newest first). 244 reverse(hashes) 245 return hashes 246 } 247 248 func contains(t []string, s string) bool { 249 for _, s2 := range t { 250 if s2 == s { 251 return true 252 } 253 } 254 return false 255 } 256 257 func reverse(s []string) { 258 for i := 0; i < len(s)/2; i++ { 259 j := len(s) - i - 1 260 s[i], s[j] = s[j], s[i] 261 } 262 } 263 264 // A CommitRun provides summary information for commits [StartCommitNum, StartCommitNum + PerfRunLength). 265 // Descendant of Package. 266 type CommitRun struct { 267 PackagePath string // (empty for main repo commits) 268 StartCommitNum int 269 Hash []string `datastore:",noindex"` 270 User []string `datastore:",noindex"` 271 Desc []string `datastore:",noindex"` // Only first line. 272 Time []time.Time `datastore:",noindex"` 273 NeedsBenchmarking []bool `datastore:",noindex"` 274 } 275 276 func (cr *CommitRun) Key(c appengine.Context) *datastore.Key { 277 p := Package{Path: cr.PackagePath} 278 key := strconv.Itoa(cr.StartCommitNum) 279 return datastore.NewKey(c, "CommitRun", key, 0, p.Key(c)) 280 } 281 282 // GetCommitRun loads and returns CommitRun that contains information 283 // for commit commitNum. 284 func GetCommitRun(c appengine.Context, commitNum int) (*CommitRun, error) { 285 cr := &CommitRun{StartCommitNum: commitNum / PerfRunLength * PerfRunLength} 286 err := datastore.Get(c, cr.Key(c), cr) 287 if err != nil && err != datastore.ErrNoSuchEntity { 288 return nil, fmt.Errorf("getting CommitRun: %v", err) 289 } 290 if len(cr.Hash) != PerfRunLength { 291 cr.Hash = make([]string, PerfRunLength) 292 cr.User = make([]string, PerfRunLength) 293 cr.Desc = make([]string, PerfRunLength) 294 cr.Time = make([]time.Time, PerfRunLength) 295 cr.NeedsBenchmarking = make([]bool, PerfRunLength) 296 } 297 return cr, nil 298 } 299 300 func (cr *CommitRun) AddCommit(c appengine.Context, com *Commit) error { 301 if com.Num < cr.StartCommitNum || com.Num >= cr.StartCommitNum+PerfRunLength { 302 return fmt.Errorf("AddCommit: commit num %v out of range [%v, %v)", 303 com.Num, cr.StartCommitNum, cr.StartCommitNum+PerfRunLength) 304 } 305 i := com.Num - cr.StartCommitNum 306 // Be careful with string lengths, 307 // we need to fit 1024 commits into 1 MB. 308 cr.Hash[i] = com.Hash 309 cr.User[i] = shortDesc(com.User) 310 cr.Desc[i] = shortDesc(com.Desc) 311 cr.Time[i] = com.Time 312 cr.NeedsBenchmarking[i] = com.NeedsBenchmarking 313 if _, err := datastore.Put(c, cr.Key(c), cr); err != nil { 314 return fmt.Errorf("putting CommitRun: %v", err) 315 } 316 return nil 317 } 318 319 // GetCommits returns [startCommitNum, startCommitNum+n) commits. 320 // Commits information is partial (obtained from CommitRun), 321 // do not store them back into datastore. 322 func GetCommits(c appengine.Context, startCommitNum, n int) ([]*Commit, error) { 323 if startCommitNum < 0 || n <= 0 { 324 return nil, fmt.Errorf("GetCommits: invalid args (%v, %v)", startCommitNum, n) 325 } 326 327 p := &Package{} 328 t := datastore.NewQuery("CommitRun"). 329 Ancestor(p.Key(c)). 330 Filter("StartCommitNum >=", startCommitNum/PerfRunLength*PerfRunLength). 331 Order("StartCommitNum"). 332 Limit(100). 333 Run(c) 334 335 res := make([]*Commit, n) 336 for { 337 cr := new(CommitRun) 338 _, err := t.Next(cr) 339 if err == datastore.Done { 340 break 341 } 342 if err != nil { 343 return nil, err 344 } 345 if cr.StartCommitNum >= startCommitNum+n { 346 break 347 } 348 // Calculate start index for copying. 349 i := 0 350 if cr.StartCommitNum < startCommitNum { 351 i = startCommitNum - cr.StartCommitNum 352 } 353 // Calculate end index for copying. 354 e := PerfRunLength 355 if cr.StartCommitNum+e > startCommitNum+n { 356 e = startCommitNum + n - cr.StartCommitNum 357 } 358 for ; i < e; i++ { 359 com := new(Commit) 360 com.Hash = cr.Hash[i] 361 com.User = cr.User[i] 362 com.Desc = cr.Desc[i] 363 com.Time = cr.Time[i] 364 com.NeedsBenchmarking = cr.NeedsBenchmarking[i] 365 res[cr.StartCommitNum-startCommitNum+i] = com 366 } 367 if e != PerfRunLength { 368 break 369 } 370 } 371 return res, nil 372 } 373 374 // partsToHash converts a Commit and ResultData substrings to a Result. 375 func partsToHash(c *Commit, p []string) *Result { 376 return &Result{ 377 Builder: p[0], 378 Hash: c.Hash, 379 PackagePath: c.PackagePath, 380 GoHash: p[3], 381 OK: p[1] == "true", 382 LogHash: p[2], 383 } 384 } 385 386 // A Result describes a build result for a Commit on an OS/architecture. 387 // 388 // Each Result entity is a descendant of its associated Package entity. 389 type Result struct { 390 PackagePath string // (empty for Go commits) 391 Builder string // "os-arch[-note]" 392 Hash string 393 394 // The Go Commit this was built against (empty for Go commits). 395 GoHash string 396 397 OK bool 398 Log string `datastore:"-"` // for JSON unmarshaling only 399 LogHash string `datastore:",noindex"` // Key to the Log record. 400 401 RunTime int64 // time to build+test in nanoseconds 402 } 403 404 func (r *Result) Key(c appengine.Context) *datastore.Key { 405 p := Package{Path: r.PackagePath} 406 key := r.Builder + "|" + r.PackagePath + "|" + r.Hash + "|" + r.GoHash 407 return datastore.NewKey(c, "Result", key, 0, p.Key(c)) 408 } 409 410 func (r *Result) Valid() error { 411 if !validHash(r.Hash) { 412 return errors.New("invalid Hash") 413 } 414 if r.PackagePath != "" && !validHash(r.GoHash) { 415 return errors.New("invalid GoHash") 416 } 417 return nil 418 } 419 420 // Data returns the Result in string format 421 // to be stored in Commit's ResultData field. 422 func (r *Result) Data() string { 423 return fmt.Sprintf("%v|%v|%v|%v", r.Builder, r.OK, r.LogHash, r.GoHash) 424 } 425 426 // A PerfResult describes all benchmarking result for a Commit. 427 // Descendant of Package. 428 type PerfResult struct { 429 PackagePath string 430 CommitHash string 431 CommitNum int 432 Data []string `datastore:",noindex"` // "builder|benchmark|ok|metric1=val1|metric2=val2|file:log=hash|file:cpuprof=hash" 433 434 // Local cache with parsed Data. 435 // Maps builder->benchmark->ParsedPerfResult. 436 parsedData map[string]map[string]*ParsedPerfResult 437 } 438 439 type ParsedPerfResult struct { 440 OK bool 441 Metrics map[string]uint64 442 Artifacts map[string]string 443 } 444 445 func (r *PerfResult) Key(c appengine.Context) *datastore.Key { 446 p := Package{Path: r.PackagePath} 447 key := r.CommitHash 448 return datastore.NewKey(c, "PerfResult", key, 0, p.Key(c)) 449 } 450 451 // AddResult add the benchmarking result to r. 452 // Existing result for the same builder/benchmark is replaced if already exists. 453 // Returns whether the result was already present. 454 func (r *PerfResult) AddResult(req *PerfRequest) bool { 455 present := false 456 str := fmt.Sprintf("%v|%v|", req.Builder, req.Benchmark) 457 for i, s := range r.Data { 458 if strings.HasPrefix(s, str) { 459 present = true 460 last := len(r.Data) - 1 461 r.Data[i] = r.Data[last] 462 r.Data = r.Data[:last] 463 break 464 } 465 } 466 ok := "ok" 467 if !req.OK { 468 ok = "false" 469 } 470 str += ok 471 for _, m := range req.Metrics { 472 str += fmt.Sprintf("|%v=%v", m.Type, m.Val) 473 } 474 for _, a := range req.Artifacts { 475 str += fmt.Sprintf("|file:%v=%v", a.Type, a.Body) 476 } 477 r.Data = append(r.Data, str) 478 r.parsedData = nil 479 return present 480 } 481 482 func (r *PerfResult) ParseData() map[string]map[string]*ParsedPerfResult { 483 if r.parsedData != nil { 484 return r.parsedData 485 } 486 res := make(map[string]map[string]*ParsedPerfResult) 487 for _, str := range r.Data { 488 ss := strings.Split(str, "|") 489 builder := ss[0] 490 bench := ss[1] 491 ok := ss[2] 492 m := res[builder] 493 if m == nil { 494 m = make(map[string]*ParsedPerfResult) 495 res[builder] = m 496 } 497 var p ParsedPerfResult 498 p.OK = ok == "ok" 499 p.Metrics = make(map[string]uint64) 500 p.Artifacts = make(map[string]string) 501 for _, entry := range ss[3:] { 502 if strings.HasPrefix(entry, "file:") { 503 ss1 := strings.Split(entry[len("file:"):], "=") 504 p.Artifacts[ss1[0]] = ss1[1] 505 } else { 506 ss1 := strings.Split(entry, "=") 507 val, _ := strconv.ParseUint(ss1[1], 10, 64) 508 p.Metrics[ss1[0]] = val 509 } 510 } 511 m[bench] = &p 512 } 513 r.parsedData = res 514 return res 515 } 516 517 // A PerfMetricRun entity holds a set of metric values for builder/benchmark/metric 518 // for commits [StartCommitNum, StartCommitNum + PerfRunLength). 519 // Descendant of Package. 520 type PerfMetricRun struct { 521 PackagePath string 522 Builder string 523 Benchmark string 524 Metric string // e.g. realtime, cputime, gc-pause 525 StartCommitNum int 526 Vals []int64 `datastore:",noindex"` 527 } 528 529 func (m *PerfMetricRun) Key(c appengine.Context) *datastore.Key { 530 p := Package{Path: m.PackagePath} 531 key := m.Builder + "|" + m.Benchmark + "|" + m.Metric + "|" + strconv.Itoa(m.StartCommitNum) 532 return datastore.NewKey(c, "PerfMetricRun", key, 0, p.Key(c)) 533 } 534 535 // GetPerfMetricRun loads and returns PerfMetricRun that contains information 536 // for commit commitNum. 537 func GetPerfMetricRun(c appengine.Context, builder, benchmark, metric string, commitNum int) (*PerfMetricRun, error) { 538 startCommitNum := commitNum / PerfRunLength * PerfRunLength 539 m := &PerfMetricRun{Builder: builder, Benchmark: benchmark, Metric: metric, StartCommitNum: startCommitNum} 540 err := datastore.Get(c, m.Key(c), m) 541 if err != nil && err != datastore.ErrNoSuchEntity { 542 return nil, fmt.Errorf("getting PerfMetricRun: %v", err) 543 } 544 if len(m.Vals) != PerfRunLength { 545 m.Vals = make([]int64, PerfRunLength) 546 } 547 return m, nil 548 } 549 550 func (m *PerfMetricRun) AddMetric(c appengine.Context, commitNum int, v uint64) error { 551 if commitNum < m.StartCommitNum || commitNum >= m.StartCommitNum+PerfRunLength { 552 return fmt.Errorf("AddMetric: CommitNum %v out of range [%v, %v)", 553 commitNum, m.StartCommitNum, m.StartCommitNum+PerfRunLength) 554 } 555 m.Vals[commitNum-m.StartCommitNum] = int64(v) 556 if _, err := datastore.Put(c, m.Key(c), m); err != nil { 557 return fmt.Errorf("putting PerfMetricRun: %v", err) 558 } 559 return nil 560 } 561 562 // GetPerfMetricsForCommits returns perf metrics for builder/benchmark/metric 563 // and commits [startCommitNum, startCommitNum+n). 564 func GetPerfMetricsForCommits(c appengine.Context, builder, benchmark, metric string, startCommitNum, n int) ([]uint64, error) { 565 if startCommitNum < 0 || n <= 0 { 566 return nil, fmt.Errorf("GetPerfMetricsForCommits: invalid args (%v, %v)", startCommitNum, n) 567 } 568 569 p := &Package{} 570 t := datastore.NewQuery("PerfMetricRun"). 571 Ancestor(p.Key(c)). 572 Filter("Builder =", builder). 573 Filter("Benchmark =", benchmark). 574 Filter("Metric =", metric). 575 Filter("StartCommitNum >=", startCommitNum/PerfRunLength*PerfRunLength). 576 Order("StartCommitNum"). 577 Limit(100). 578 Run(c) 579 580 res := make([]uint64, n) 581 for { 582 metrics := new(PerfMetricRun) 583 _, err := t.Next(metrics) 584 if err == datastore.Done { 585 break 586 } 587 if err != nil { 588 return nil, err 589 } 590 if metrics.StartCommitNum >= startCommitNum+n { 591 break 592 } 593 // Calculate start index for copying. 594 i := 0 595 if metrics.StartCommitNum < startCommitNum { 596 i = startCommitNum - metrics.StartCommitNum 597 } 598 // Calculate end index for copying. 599 e := PerfRunLength 600 if metrics.StartCommitNum+e > startCommitNum+n { 601 e = startCommitNum + n - metrics.StartCommitNum 602 } 603 for ; i < e; i++ { 604 res[metrics.StartCommitNum-startCommitNum+i] = uint64(metrics.Vals[i]) 605 } 606 if e != PerfRunLength { 607 break 608 } 609 } 610 return res, nil 611 } 612 613 // PerfConfig holds read-mostly configuration related to benchmarking. 614 // There is only one PerfConfig entity. 615 type PerfConfig struct { 616 BuilderBench []string `datastore:",noindex"` // "builder|benchmark" pairs 617 BuilderProcs []string `datastore:",noindex"` // "builder|proc" pairs 618 BenchMetric []string `datastore:",noindex"` // "benchmark|metric" pairs 619 NoiseLevels []string `datastore:",noindex"` // "builder|benchmark|metric1=noise1|metric2=noise2" 620 621 // Local cache of "builder|benchmark|metric" -> noise. 622 noise map[string]float64 623 } 624 625 func PerfConfigKey(c appengine.Context) *datastore.Key { 626 p := Package{} 627 return datastore.NewKey(c, "PerfConfig", "PerfConfig", 0, p.Key(c)) 628 } 629 630 const perfConfigCacheKey = "perf-config" 631 632 func GetPerfConfig(c appengine.Context, r *http.Request) (*PerfConfig, error) { 633 pc := new(PerfConfig) 634 now := cache.Now(c) 635 if cache.Get(r, now, perfConfigCacheKey, pc) { 636 return pc, nil 637 } 638 err := datastore.Get(c, PerfConfigKey(c), pc) 639 if err != nil && err != datastore.ErrNoSuchEntity { 640 return nil, fmt.Errorf("GetPerfConfig: %v", err) 641 } 642 cache.Set(r, now, perfConfigCacheKey, pc) 643 return pc, nil 644 } 645 646 func (pc *PerfConfig) NoiseLevel(builder, benchmark, metric string) float64 { 647 if pc.noise == nil { 648 pc.noise = make(map[string]float64) 649 for _, str := range pc.NoiseLevels { 650 split := strings.Split(str, "|") 651 builderBench := split[0] + "|" + split[1] 652 for _, entry := range split[2:] { 653 metricValue := strings.Split(entry, "=") 654 noise, _ := strconv.ParseFloat(metricValue[1], 64) 655 pc.noise[builderBench+"|"+metricValue[0]] = noise 656 } 657 } 658 } 659 me := fmt.Sprintf("%v|%v|%v", builder, benchmark, metric) 660 n := pc.noise[me] 661 if n == 0 { 662 // Use a very conservative value 663 // until we have learned the real noise level. 664 n = 200 665 } 666 return n 667 } 668 669 // UpdatePerfConfig updates the PerfConfig entity with results of benchmarking. 670 // Returns whether it's a benchmark that we have not yet seem on the builder. 671 func UpdatePerfConfig(c appengine.Context, r *http.Request, req *PerfRequest) (newBenchmark bool, err error) { 672 pc, err := GetPerfConfig(c, r) 673 if err != nil { 674 return false, err 675 } 676 677 modified := false 678 add := func(arr *[]string, str string) { 679 for _, s := range *arr { 680 if s == str { 681 return 682 } 683 } 684 *arr = append(*arr, str) 685 modified = true 686 return 687 } 688 689 BenchProcs := strings.Split(req.Benchmark, "-") 690 benchmark := BenchProcs[0] 691 procs := "1" 692 if len(BenchProcs) > 1 { 693 procs = BenchProcs[1] 694 } 695 696 add(&pc.BuilderBench, req.Builder+"|"+benchmark) 697 newBenchmark = modified 698 add(&pc.BuilderProcs, req.Builder+"|"+procs) 699 for _, m := range req.Metrics { 700 add(&pc.BenchMetric, benchmark+"|"+m.Type) 701 } 702 703 if modified { 704 if _, err := datastore.Put(c, PerfConfigKey(c), pc); err != nil { 705 return false, fmt.Errorf("putting PerfConfig: %v", err) 706 } 707 cache.Tick(c) 708 } 709 return newBenchmark, nil 710 } 711 712 type MetricList []string 713 714 func (l MetricList) Len() int { 715 return len(l) 716 } 717 718 func (l MetricList) Less(i, j int) bool { 719 bi := strings.HasPrefix(l[i], "build-") || strings.HasPrefix(l[i], "binary-") 720 bj := strings.HasPrefix(l[j], "build-") || strings.HasPrefix(l[j], "binary-") 721 if bi == bj { 722 return l[i] < l[j] 723 } 724 return !bi 725 } 726 727 func (l MetricList) Swap(i, j int) { 728 l[i], l[j] = l[j], l[i] 729 } 730 731 func collectList(all []string, idx int, second string) (res []string) { 732 m := make(map[string]bool) 733 for _, str := range all { 734 ss := strings.Split(str, "|") 735 v := ss[idx] 736 v2 := ss[1-idx] 737 if (second == "" || second == v2) && !m[v] { 738 m[v] = true 739 res = append(res, v) 740 } 741 } 742 sort.Sort(MetricList(res)) 743 return res 744 } 745 746 func (pc *PerfConfig) BuildersForBenchmark(bench string) []string { 747 return collectList(pc.BuilderBench, 0, bench) 748 } 749 750 func (pc *PerfConfig) BenchmarksForBuilder(builder string) []string { 751 return collectList(pc.BuilderBench, 1, builder) 752 } 753 754 func (pc *PerfConfig) MetricsForBenchmark(bench string) []string { 755 return collectList(pc.BenchMetric, 1, bench) 756 } 757 758 func (pc *PerfConfig) BenchmarkProcList() (res []string) { 759 bl := pc.BenchmarksForBuilder("") 760 pl := pc.ProcList("") 761 for _, b := range bl { 762 for _, p := range pl { 763 res = append(res, fmt.Sprintf("%v-%v", b, p)) 764 } 765 } 766 return res 767 } 768 769 func (pc *PerfConfig) ProcList(builder string) []int { 770 ss := collectList(pc.BuilderProcs, 1, builder) 771 var procs []int 772 for _, s := range ss { 773 p, _ := strconv.ParseInt(s, 10, 32) 774 procs = append(procs, int(p)) 775 } 776 sort.Ints(procs) 777 return procs 778 } 779 780 // A PerfTodo contains outstanding commits for benchmarking for a builder. 781 // Descendant of Package. 782 type PerfTodo struct { 783 PackagePath string // (empty for main repo commits) 784 Builder string 785 CommitNums []int `datastore:",noindex"` // LIFO queue of commits to benchmark. 786 } 787 788 func (todo *PerfTodo) Key(c appengine.Context) *datastore.Key { 789 p := Package{Path: todo.PackagePath} 790 key := todo.Builder 791 return datastore.NewKey(c, "PerfTodo", key, 0, p.Key(c)) 792 } 793 794 // AddCommitToPerfTodo adds the commit to all existing PerfTodo entities. 795 func AddCommitToPerfTodo(c appengine.Context, com *Commit) error { 796 var todos []*PerfTodo 797 _, err := datastore.NewQuery("PerfTodo"). 798 Ancestor((&Package{}).Key(c)). 799 GetAll(c, &todos) 800 if err != nil { 801 return fmt.Errorf("fetching PerfTodo's: %v", err) 802 } 803 for _, todo := range todos { 804 todo.CommitNums = append(todo.CommitNums, com.Num) 805 _, err = datastore.Put(c, todo.Key(c), todo) 806 if err != nil { 807 return fmt.Errorf("updating PerfTodo: %v", err) 808 } 809 } 810 return nil 811 } 812 813 // A Log is a gzip-compressed log file stored under the SHA1 hash of the 814 // uncompressed log text. 815 type Log struct { 816 CompressedLog []byte 817 } 818 819 func (l *Log) Text() ([]byte, error) { 820 d, err := gzip.NewReader(bytes.NewBuffer(l.CompressedLog)) 821 if err != nil { 822 return nil, fmt.Errorf("reading log data: %v", err) 823 } 824 b, err := ioutil.ReadAll(d) 825 if err != nil { 826 return nil, fmt.Errorf("reading log data: %v", err) 827 } 828 return b, nil 829 } 830 831 func PutLog(c appengine.Context, text string) (hash string, err error) { 832 h := sha1.New() 833 io.WriteString(h, text) 834 b := new(bytes.Buffer) 835 z, _ := gzip.NewWriterLevel(b, gzip.BestCompression) 836 io.WriteString(z, text) 837 z.Close() 838 hash = fmt.Sprintf("%x", h.Sum(nil)) 839 key := datastore.NewKey(c, "Log", hash, 0, nil) 840 _, err = datastore.Put(c, key, &Log{b.Bytes()}) 841 return 842 } 843 844 // A Tag is used to keep track of the most recent Go weekly and release tags. 845 // Typically there will be one Tag entity for each kind of hg tag. 846 type Tag struct { 847 Kind string // "weekly", "release", or "tip" 848 Name string // the tag itself (for example: "release.r60") 849 Hash string 850 } 851 852 func (t *Tag) Key(c appengine.Context) *datastore.Key { 853 p := &Package{} 854 return datastore.NewKey(c, "Tag", t.Kind, 0, p.Key(c)) 855 } 856 857 func (t *Tag) Valid() error { 858 if t.Kind != "weekly" && t.Kind != "release" && t.Kind != "tip" { 859 return errors.New("invalid Kind") 860 } 861 if !validHash(t.Hash) { 862 return errors.New("invalid Hash") 863 } 864 return nil 865 } 866 867 // Commit returns the Commit that corresponds with this Tag. 868 func (t *Tag) Commit(c appengine.Context) (*Commit, error) { 869 com := &Commit{Hash: t.Hash} 870 err := datastore.Get(c, com.Key(c), com) 871 return com, err 872 } 873 874 // GetTag fetches a Tag by name from the datastore. 875 func GetTag(c appengine.Context, tag string) (*Tag, error) { 876 t := &Tag{Kind: tag} 877 if err := datastore.Get(c, t.Key(c), t); err != nil { 878 if err == datastore.ErrNoSuchEntity { 879 return nil, errors.New("tag not found: " + tag) 880 } 881 return nil, err 882 } 883 if err := t.Valid(); err != nil { 884 return nil, err 885 } 886 return t, nil 887 } 888 889 // Packages returns packages of the specified kind. 890 // Kind must be one of "external" or "subrepo". 891 func Packages(c appengine.Context, kind string) ([]*Package, error) { 892 switch kind { 893 case "external", "subrepo": 894 default: 895 return nil, errors.New(`kind must be one of "external" or "subrepo"`) 896 } 897 var pkgs []*Package 898 q := datastore.NewQuery("Package").Filter("Kind=", kind) 899 for t := q.Run(c); ; { 900 pkg := new(Package) 901 _, err := t.Next(pkg) 902 if err == datastore.Done { 903 break 904 } else if err != nil { 905 return nil, err 906 } 907 if pkg.Path != "" { 908 pkgs = append(pkgs, pkg) 909 } 910 } 911 return pkgs, nil 912 }