aboutsummaryrefslogtreecommitdiff
path: root/dashboard/app/build
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard/app/build')
-rw-r--r--dashboard/app/build/build.go911
-rw-r--r--dashboard/app/build/dash.go118
-rw-r--r--dashboard/app/build/handler.go906
-rw-r--r--dashboard/app/build/init.go46
-rw-r--r--dashboard/app/build/notify.go378
-rw-r--r--dashboard/app/build/notify.txt9
-rw-r--r--dashboard/app/build/perf.go312
-rw-r--r--dashboard/app/build/perf_changes.go282
-rw-r--r--dashboard/app/build/perf_changes.html89
-rw-r--r--dashboard/app/build/perf_detail.go221
-rw-r--r--dashboard/app/build/perf_detail.html101
-rw-r--r--dashboard/app/build/perf_graph.go270
-rw-r--r--dashboard/app/build/perf_graph.html120
-rw-r--r--dashboard/app/build/perf_learn.go186
-rw-r--r--dashboard/app/build/perf_learn.html45
-rw-r--r--dashboard/app/build/perf_notify.txt13
-rw-r--r--dashboard/app/build/test.go378
-rw-r--r--dashboard/app/build/ui.go460
-rw-r--r--dashboard/app/build/ui.html210
-rw-r--r--dashboard/app/build/update.go117
20 files changed, 0 insertions, 5172 deletions
diff --git a/dashboard/app/build/build.go b/dashboard/app/build/build.go
deleted file mode 100644
index 90ca344..0000000
--- a/dashboard/app/build/build.go
+++ /dev/null
@@ -1,911 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-import (
- "bytes"
- "compress/gzip"
- "crypto/sha1"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "sort"
- "strconv"
- "strings"
- "time"
-
- "appengine"
- "appengine/datastore"
-
- "cache"
-)
-
-const (
- maxDatastoreStringLen = 500
- PerfRunLength = 1024
-)
-
-// A Package describes a package that is listed on the dashboard.
-type Package struct {
- Kind string // "subrepo", "external", or empty for the main Go tree
- Name string
- Path string // (empty for the main Go tree)
- NextNum int // Num of the next head Commit
-}
-
-func (p *Package) String() string {
- return fmt.Sprintf("%s: %q", p.Path, p.Name)
-}
-
-func (p *Package) Key(c appengine.Context) *datastore.Key {
- key := p.Path
- if key == "" {
- key = "go"
- }
- return datastore.NewKey(c, "Package", key, 0, nil)
-}
-
-// LastCommit returns the most recent Commit for this Package.
-func (p *Package) LastCommit(c appengine.Context) (*Commit, error) {
- var commits []*Commit
- _, err := datastore.NewQuery("Commit").
- Ancestor(p.Key(c)).
- Order("-Time").
- Limit(1).
- GetAll(c, &commits)
- if err != nil {
- return nil, err
- }
- if len(commits) != 1 {
- return nil, datastore.ErrNoSuchEntity
- }
- return commits[0], nil
-}
-
-// GetPackage fetches a Package by path from the datastore.
-func GetPackage(c appengine.Context, path string) (*Package, error) {
- p := &Package{Path: path}
- err := datastore.Get(c, p.Key(c), p)
- if err == datastore.ErrNoSuchEntity {
- return nil, fmt.Errorf("package %q not found", path)
- }
- return p, err
-}
-
-// A Commit describes an individual commit in a package.
-//
-// Each Commit entity is a descendant of its associated Package entity.
-// In other words, all Commits with the same PackagePath belong to the same
-// datastore entity group.
-type Commit struct {
- PackagePath string // (empty for main repo commits)
- Hash string
- ParentHash string
- Num int // Internal monotonic counter unique to this package.
-
- User string
- Desc string `datastore:",noindex"`
- Time time.Time
- NeedsBenchmarking bool
- TryPatch bool
-
- // ResultData is the Data string of each build Result for this Commit.
- // For non-Go commits, only the Results for the current Go tip, weekly,
- // and release Tags are stored here. This is purely de-normalized data.
- // The complete data set is stored in Result entities.
- ResultData []string `datastore:",noindex"`
-
- // PerfResults holds a set of “builder|benchmark” tuples denoting
- // what benchmarks have been executed on the commit.
- PerfResults []string `datastore:",noindex"`
-
- FailNotificationSent bool
-}
-
-func (com *Commit) Key(c appengine.Context) *datastore.Key {
- if com.Hash == "" {
- panic("tried Key on Commit with empty Hash")
- }
- p := Package{Path: com.PackagePath}
- key := com.PackagePath + "|" + com.Hash
- return datastore.NewKey(c, "Commit", key, 0, p.Key(c))
-}
-
-func (c *Commit) Valid() error {
- if !validHash(c.Hash) {
- return errors.New("invalid Hash")
- }
- if c.ParentHash != "" && !validHash(c.ParentHash) { // empty is OK
- return errors.New("invalid ParentHash")
- }
- return nil
-}
-
-func putCommit(c appengine.Context, com *Commit) error {
- if err := com.Valid(); err != nil {
- return fmt.Errorf("putting Commit: %v", err)
- }
- if com.Num == 0 && com.ParentHash != "0000" { // 0000 is used in tests
- return fmt.Errorf("putting Commit: invalid Num (must be > 0)")
- }
- if _, err := datastore.Put(c, com.Key(c), com); err != nil {
- return fmt.Errorf("putting Commit: %v", err)
- }
- return nil
-}
-
-// each result line is approx 105 bytes. This constant is a tradeoff between
-// build history and the AppEngine datastore limit of 1mb.
-const maxResults = 1000
-
-// AddResult adds the denormalized Result data to the Commit's Result field.
-// It must be called from inside a datastore transaction.
-func (com *Commit) AddResult(c appengine.Context, r *Result) error {
- if err := datastore.Get(c, com.Key(c), com); err != nil {
- return fmt.Errorf("getting Commit: %v", err)
- }
-
- var resultExists bool
- for i, s := range com.ResultData {
- // if there already exists result data for this builder at com, overwrite it.
- if strings.HasPrefix(s, r.Builder+"|") && strings.HasSuffix(s, "|"+r.GoHash) {
- resultExists = true
- com.ResultData[i] = r.Data()
- }
- }
- if !resultExists {
- // otherwise, add the new result data for this builder.
- com.ResultData = trim(append(com.ResultData, r.Data()), maxResults)
- }
- return putCommit(c, com)
-}
-
-// AddPerfResult remembers that the builder has run the benchmark on the commit.
-// It must be called from inside a datastore transaction.
-func (com *Commit) AddPerfResult(c appengine.Context, builder, benchmark string) error {
- if err := datastore.Get(c, com.Key(c), com); err != nil {
- return fmt.Errorf("getting Commit: %v", err)
- }
- if !com.NeedsBenchmarking {
- return fmt.Errorf("trying to add perf result to Commit(%v) that does not require benchmarking", com.Hash)
- }
- s := builder + "|" + benchmark
- for _, v := range com.PerfResults {
- if v == s {
- return nil
- }
- }
- com.PerfResults = append(com.PerfResults, s)
- return putCommit(c, com)
-}
-
-func trim(s []string, n int) []string {
- l := min(len(s), n)
- return s[len(s)-l:]
-}
-
-func min(a, b int) int {
- if a < b {
- return a
- }
- return b
-}
-
-// Result returns the build Result for this Commit for the given builder/goHash.
-func (c *Commit) Result(builder, goHash string) *Result {
- for _, r := range c.ResultData {
- p := strings.SplitN(r, "|", 4)
- if len(p) != 4 || p[0] != builder || p[3] != goHash {
- continue
- }
- return partsToHash(c, p)
- }
- return nil
-}
-
-// Results returns the build Results for this Commit.
-func (c *Commit) Results() (results []*Result) {
- for _, r := range c.ResultData {
- p := strings.SplitN(r, "|", 4)
- if len(p) != 4 {
- continue
- }
- results = append(results, partsToHash(c, p))
- }
- return
-}
-
-func (c *Commit) ResultGoHashes() []string {
- // For the main repo, just return the empty string
- // (there's no corresponding main repo hash for a main repo Commit).
- // This function is only really useful for sub-repos.
- if c.PackagePath == "" {
- return []string{""}
- }
- var hashes []string
- for _, r := range c.ResultData {
- p := strings.SplitN(r, "|", 4)
- if len(p) != 4 {
- continue
- }
- // Append only new results (use linear scan to preserve order).
- if !contains(hashes, p[3]) {
- hashes = append(hashes, p[3])
- }
- }
- // Return results in reverse order (newest first).
- reverse(hashes)
- return hashes
-}
-
-func contains(t []string, s string) bool {
- for _, s2 := range t {
- if s2 == s {
- return true
- }
- }
- return false
-}
-
-func reverse(s []string) {
- for i := 0; i < len(s)/2; i++ {
- j := len(s) - i - 1
- s[i], s[j] = s[j], s[i]
- }
-}
-
-// A CommitRun provides summary information for commits [StartCommitNum, StartCommitNum + PerfRunLength).
-// Descendant of Package.
-type CommitRun struct {
- PackagePath string // (empty for main repo commits)
- StartCommitNum int
- Hash []string `datastore:",noindex"`
- User []string `datastore:",noindex"`
- Desc []string `datastore:",noindex"` // Only first line.
- Time []time.Time `datastore:",noindex"`
- NeedsBenchmarking []bool `datastore:",noindex"`
-}
-
-func (cr *CommitRun) Key(c appengine.Context) *datastore.Key {
- p := Package{Path: cr.PackagePath}
- key := strconv.Itoa(cr.StartCommitNum)
- return datastore.NewKey(c, "CommitRun", key, 0, p.Key(c))
-}
-
-// GetCommitRun loads and returns CommitRun that contains information
-// for commit commitNum.
-func GetCommitRun(c appengine.Context, commitNum int) (*CommitRun, error) {
- cr := &CommitRun{StartCommitNum: commitNum / PerfRunLength * PerfRunLength}
- err := datastore.Get(c, cr.Key(c), cr)
- if err != nil && err != datastore.ErrNoSuchEntity {
- return nil, fmt.Errorf("getting CommitRun: %v", err)
- }
- if len(cr.Hash) != PerfRunLength {
- cr.Hash = make([]string, PerfRunLength)
- cr.User = make([]string, PerfRunLength)
- cr.Desc = make([]string, PerfRunLength)
- cr.Time = make([]time.Time, PerfRunLength)
- cr.NeedsBenchmarking = make([]bool, PerfRunLength)
- }
- return cr, nil
-}
-
-func (cr *CommitRun) AddCommit(c appengine.Context, com *Commit) error {
- if com.Num < cr.StartCommitNum || com.Num >= cr.StartCommitNum+PerfRunLength {
- return fmt.Errorf("AddCommit: commit num %v out of range [%v, %v)",
- com.Num, cr.StartCommitNum, cr.StartCommitNum+PerfRunLength)
- }
- i := com.Num - cr.StartCommitNum
- // Be careful with string lengths,
- // we need to fit 1024 commits into 1 MB.
- cr.Hash[i] = com.Hash
- cr.User[i] = shortDesc(com.User)
- cr.Desc[i] = shortDesc(com.Desc)
- cr.Time[i] = com.Time
- cr.NeedsBenchmarking[i] = com.NeedsBenchmarking
- if _, err := datastore.Put(c, cr.Key(c), cr); err != nil {
- return fmt.Errorf("putting CommitRun: %v", err)
- }
- return nil
-}
-
-// GetCommits returns [startCommitNum, startCommitNum+n) commits.
-// Commits information is partial (obtained from CommitRun),
-// do not store them back into datastore.
-func GetCommits(c appengine.Context, startCommitNum, n int) ([]*Commit, error) {
- if startCommitNum < 0 || n <= 0 {
- return nil, fmt.Errorf("GetCommits: invalid args (%v, %v)", startCommitNum, n)
- }
-
- p := &Package{}
- t := datastore.NewQuery("CommitRun").
- Ancestor(p.Key(c)).
- Filter("StartCommitNum >=", startCommitNum/PerfRunLength*PerfRunLength).
- Order("StartCommitNum").
- Limit(100).
- Run(c)
-
- res := make([]*Commit, n)
- for {
- cr := new(CommitRun)
- _, err := t.Next(cr)
- if err == datastore.Done {
- break
- }
- if err != nil {
- return nil, err
- }
- if cr.StartCommitNum >= startCommitNum+n {
- break
- }
- // Calculate start index for copying.
- i := 0
- if cr.StartCommitNum < startCommitNum {
- i = startCommitNum - cr.StartCommitNum
- }
- // Calculate end index for copying.
- e := PerfRunLength
- if cr.StartCommitNum+e > startCommitNum+n {
- e = startCommitNum + n - cr.StartCommitNum
- }
- for ; i < e; i++ {
- com := new(Commit)
- com.Hash = cr.Hash[i]
- com.User = cr.User[i]
- com.Desc = cr.Desc[i]
- com.Time = cr.Time[i]
- com.NeedsBenchmarking = cr.NeedsBenchmarking[i]
- res[cr.StartCommitNum-startCommitNum+i] = com
- }
- if e != PerfRunLength {
- break
- }
- }
- return res, nil
-}
-
-// partsToHash converts a Commit and ResultData substrings to a Result.
-func partsToHash(c *Commit, p []string) *Result {
- return &Result{
- Builder: p[0],
- Hash: c.Hash,
- PackagePath: c.PackagePath,
- GoHash: p[3],
- OK: p[1] == "true",
- LogHash: p[2],
- }
-}
-
-// A Result describes a build result for a Commit on an OS/architecture.
-//
-// Each Result entity is a descendant of its associated Package entity.
-type Result struct {
- PackagePath string // (empty for Go commits)
- Builder string // "os-arch[-note]"
- Hash string
-
- // The Go Commit this was built against (empty for Go commits).
- GoHash string
-
- OK bool
- Log string `datastore:"-"` // for JSON unmarshaling only
- LogHash string `datastore:",noindex"` // Key to the Log record.
-
- RunTime int64 // time to build+test in nanoseconds
-}
-
-func (r *Result) Key(c appengine.Context) *datastore.Key {
- p := Package{Path: r.PackagePath}
- key := r.Builder + "|" + r.PackagePath + "|" + r.Hash + "|" + r.GoHash
- return datastore.NewKey(c, "Result", key, 0, p.Key(c))
-}
-
-func (r *Result) Valid() error {
- if !validHash(r.Hash) {
- return errors.New("invalid Hash")
- }
- if r.PackagePath != "" && !validHash(r.GoHash) {
- return errors.New("invalid GoHash")
- }
- return nil
-}
-
-// Data returns the Result in string format
-// to be stored in Commit's ResultData field.
-func (r *Result) Data() string {
- return fmt.Sprintf("%v|%v|%v|%v", r.Builder, r.OK, r.LogHash, r.GoHash)
-}
-
-// A PerfResult describes all benchmarking result for a Commit.
-// Descendant of Package.
-type PerfResult struct {
- PackagePath string
- CommitHash string
- CommitNum int
- Data []string `datastore:",noindex"` // "builder|benchmark|ok|metric1=val1|metric2=val2|file:log=hash|file:cpuprof=hash"
-
- // Local cache with parsed Data.
- // Maps builder->benchmark->ParsedPerfResult.
- parsedData map[string]map[string]*ParsedPerfResult
-}
-
-type ParsedPerfResult struct {
- OK bool
- Metrics map[string]uint64
- Artifacts map[string]string
-}
-
-func (r *PerfResult) Key(c appengine.Context) *datastore.Key {
- p := Package{Path: r.PackagePath}
- key := r.CommitHash
- return datastore.NewKey(c, "PerfResult", key, 0, p.Key(c))
-}
-
-// AddResult add the benchmarking result to r.
-// Existing result for the same builder/benchmark is replaced if already exists.
-// Returns whether the result was already present.
-func (r *PerfResult) AddResult(req *PerfRequest) bool {
- present := false
- str := fmt.Sprintf("%v|%v|", req.Builder, req.Benchmark)
- for i, s := range r.Data {
- if strings.HasPrefix(s, str) {
- present = true
- last := len(r.Data) - 1
- r.Data[i] = r.Data[last]
- r.Data = r.Data[:last]
- break
- }
- }
- ok := "ok"
- if !req.OK {
- ok = "false"
- }
- str += ok
- for _, m := range req.Metrics {
- str += fmt.Sprintf("|%v=%v", m.Type, m.Val)
- }
- for _, a := range req.Artifacts {
- str += fmt.Sprintf("|file:%v=%v", a.Type, a.Body)
- }
- r.Data = append(r.Data, str)
- r.parsedData = nil
- return present
-}
-
-func (r *PerfResult) ParseData() map[string]map[string]*ParsedPerfResult {
- if r.parsedData != nil {
- return r.parsedData
- }
- res := make(map[string]map[string]*ParsedPerfResult)
- for _, str := range r.Data {
- ss := strings.Split(str, "|")
- builder := ss[0]
- bench := ss[1]
- ok := ss[2]
- m := res[builder]
- if m == nil {
- m = make(map[string]*ParsedPerfResult)
- res[builder] = m
- }
- var p ParsedPerfResult
- p.OK = ok == "ok"
- p.Metrics = make(map[string]uint64)
- p.Artifacts = make(map[string]string)
- for _, entry := range ss[3:] {
- if strings.HasPrefix(entry, "file:") {
- ss1 := strings.Split(entry[len("file:"):], "=")
- p.Artifacts[ss1[0]] = ss1[1]
- } else {
- ss1 := strings.Split(entry, "=")
- val, _ := strconv.ParseUint(ss1[1], 10, 64)
- p.Metrics[ss1[0]] = val
- }
- }
- m[bench] = &p
- }
- r.parsedData = res
- return res
-}
-
-// A PerfMetricRun entity holds a set of metric values for builder/benchmark/metric
-// for commits [StartCommitNum, StartCommitNum + PerfRunLength).
-// Descendant of Package.
-type PerfMetricRun struct {
- PackagePath string
- Builder string
- Benchmark string
- Metric string // e.g. realtime, cputime, gc-pause
- StartCommitNum int
- Vals []int64 `datastore:",noindex"`
-}
-
-func (m *PerfMetricRun) Key(c appengine.Context) *datastore.Key {
- p := Package{Path: m.PackagePath}
- key := m.Builder + "|" + m.Benchmark + "|" + m.Metric + "|" + strconv.Itoa(m.StartCommitNum)
- return datastore.NewKey(c, "PerfMetricRun", key, 0, p.Key(c))
-}
-
-// GetPerfMetricRun loads and returns PerfMetricRun that contains information
-// for commit commitNum.
-func GetPerfMetricRun(c appengine.Context, builder, benchmark, metric string, commitNum int) (*PerfMetricRun, error) {
- startCommitNum := commitNum / PerfRunLength * PerfRunLength
- m := &PerfMetricRun{Builder: builder, Benchmark: benchmark, Metric: metric, StartCommitNum: startCommitNum}
- err := datastore.Get(c, m.Key(c), m)
- if err != nil && err != datastore.ErrNoSuchEntity {
- return nil, fmt.Errorf("getting PerfMetricRun: %v", err)
- }
- if len(m.Vals) != PerfRunLength {
- m.Vals = make([]int64, PerfRunLength)
- }
- return m, nil
-}
-
-func (m *PerfMetricRun) AddMetric(c appengine.Context, commitNum int, v uint64) error {
- if commitNum < m.StartCommitNum || commitNum >= m.StartCommitNum+PerfRunLength {
- return fmt.Errorf("AddMetric: CommitNum %v out of range [%v, %v)",
- commitNum, m.StartCommitNum, m.StartCommitNum+PerfRunLength)
- }
- m.Vals[commitNum-m.StartCommitNum] = int64(v)
- if _, err := datastore.Put(c, m.Key(c), m); err != nil {
- return fmt.Errorf("putting PerfMetricRun: %v", err)
- }
- return nil
-}
-
-// GetPerfMetricsForCommits returns perf metrics for builder/benchmark/metric
-// and commits [startCommitNum, startCommitNum+n).
-func GetPerfMetricsForCommits(c appengine.Context, builder, benchmark, metric string, startCommitNum, n int) ([]uint64, error) {
- if startCommitNum < 0 || n <= 0 {
- return nil, fmt.Errorf("GetPerfMetricsForCommits: invalid args (%v, %v)", startCommitNum, n)
- }
-
- p := &Package{}
- t := datastore.NewQuery("PerfMetricRun").
- Ancestor(p.Key(c)).
- Filter("Builder =", builder).
- Filter("Benchmark =", benchmark).
- Filter("Metric =", metric).
- Filter("StartCommitNum >=", startCommitNum/PerfRunLength*PerfRunLength).
- Order("StartCommitNum").
- Limit(100).
- Run(c)
-
- res := make([]uint64, n)
- for {
- metrics := new(PerfMetricRun)
- _, err := t.Next(metrics)
- if err == datastore.Done {
- break
- }
- if err != nil {
- return nil, err
- }
- if metrics.StartCommitNum >= startCommitNum+n {
- break
- }
- // Calculate start index for copying.
- i := 0
- if metrics.StartCommitNum < startCommitNum {
- i = startCommitNum - metrics.StartCommitNum
- }
- // Calculate end index for copying.
- e := PerfRunLength
- if metrics.StartCommitNum+e > startCommitNum+n {
- e = startCommitNum + n - metrics.StartCommitNum
- }
- for ; i < e; i++ {
- res[metrics.StartCommitNum-startCommitNum+i] = uint64(metrics.Vals[i])
- }
- if e != PerfRunLength {
- break
- }
- }
- return res, nil
-}
-
-// PerfConfig holds read-mostly configuration related to benchmarking.
-// There is only one PerfConfig entity.
-type PerfConfig struct {
- BuilderBench []string `datastore:",noindex"` // "builder|benchmark" pairs
- BuilderProcs []string `datastore:",noindex"` // "builder|proc" pairs
- BenchMetric []string `datastore:",noindex"` // "benchmark|metric" pairs
- NoiseLevels []string `datastore:",noindex"` // "builder|benchmark|metric1=noise1|metric2=noise2"
-
- // Local cache of "builder|benchmark|metric" -> noise.
- noise map[string]float64
-}
-
-func PerfConfigKey(c appengine.Context) *datastore.Key {
- p := Package{}
- return datastore.NewKey(c, "PerfConfig", "PerfConfig", 0, p.Key(c))
-}
-
-const perfConfigCacheKey = "perf-config"
-
-func GetPerfConfig(c appengine.Context, r *http.Request) (*PerfConfig, error) {
- pc := new(PerfConfig)
- now := cache.Now(c)
- if cache.Get(r, now, perfConfigCacheKey, pc) {
- return pc, nil
- }
- err := datastore.Get(c, PerfConfigKey(c), pc)
- if err != nil && err != datastore.ErrNoSuchEntity {
- return nil, fmt.Errorf("GetPerfConfig: %v", err)
- }
- cache.Set(r, now, perfConfigCacheKey, pc)
- return pc, nil
-}
-
-func (pc *PerfConfig) NoiseLevel(builder, benchmark, metric string) float64 {
- if pc.noise == nil {
- pc.noise = make(map[string]float64)
- for _, str := range pc.NoiseLevels {
- split := strings.Split(str, "|")
- builderBench := split[0] + "|" + split[1]
- for _, entry := range split[2:] {
- metricValue := strings.Split(entry, "=")
- noise, _ := strconv.ParseFloat(metricValue[1], 64)
- pc.noise[builderBench+"|"+metricValue[0]] = noise
- }
- }
- }
- me := fmt.Sprintf("%v|%v|%v", builder, benchmark, metric)
- n := pc.noise[me]
- if n == 0 {
- // Use a very conservative value
- // until we have learned the real noise level.
- n = 200
- }
- return n
-}
-
-// UpdatePerfConfig updates the PerfConfig entity with results of benchmarking.
-// Returns whether it's a benchmark that we have not yet seem on the builder.
-func UpdatePerfConfig(c appengine.Context, r *http.Request, req *PerfRequest) (newBenchmark bool, err error) {
- pc, err := GetPerfConfig(c, r)
- if err != nil {
- return false, err
- }
-
- modified := false
- add := func(arr *[]string, str string) {
- for _, s := range *arr {
- if s == str {
- return
- }
- }
- *arr = append(*arr, str)
- modified = true
- return
- }
-
- BenchProcs := strings.Split(req.Benchmark, "-")
- benchmark := BenchProcs[0]
- procs := "1"
- if len(BenchProcs) > 1 {
- procs = BenchProcs[1]
- }
-
- add(&pc.BuilderBench, req.Builder+"|"+benchmark)
- newBenchmark = modified
- add(&pc.BuilderProcs, req.Builder+"|"+procs)
- for _, m := range req.Metrics {
- add(&pc.BenchMetric, benchmark+"|"+m.Type)
- }
-
- if modified {
- if _, err := datastore.Put(c, PerfConfigKey(c), pc); err != nil {
- return false, fmt.Errorf("putting PerfConfig: %v", err)
- }
- cache.Tick(c)
- }
- return newBenchmark, nil
-}
-
-type MetricList []string
-
-func (l MetricList) Len() int {
- return len(l)
-}
-
-func (l MetricList) Less(i, j int) bool {
- bi := strings.HasPrefix(l[i], "build-") || strings.HasPrefix(l[i], "binary-")
- bj := strings.HasPrefix(l[j], "build-") || strings.HasPrefix(l[j], "binary-")
- if bi == bj {
- return l[i] < l[j]
- }
- return !bi
-}
-
-func (l MetricList) Swap(i, j int) {
- l[i], l[j] = l[j], l[i]
-}
-
-func collectList(all []string, idx int, second string) (res []string) {
- m := make(map[string]bool)
- for _, str := range all {
- ss := strings.Split(str, "|")
- v := ss[idx]
- v2 := ss[1-idx]
- if (second == "" || second == v2) && !m[v] {
- m[v] = true
- res = append(res, v)
- }
- }
- sort.Sort(MetricList(res))
- return res
-}
-
-func (pc *PerfConfig) BuildersForBenchmark(bench string) []string {
- return collectList(pc.BuilderBench, 0, bench)
-}
-
-func (pc *PerfConfig) BenchmarksForBuilder(builder string) []string {
- return collectList(pc.BuilderBench, 1, builder)
-}
-
-func (pc *PerfConfig) MetricsForBenchmark(bench string) []string {
- return collectList(pc.BenchMetric, 1, bench)
-}
-
-func (pc *PerfConfig) BenchmarkProcList() (res []string) {
- bl := pc.BenchmarksForBuilder("")
- pl := pc.ProcList("")
- for _, b := range bl {
- for _, p := range pl {
- res = append(res, fmt.Sprintf("%v-%v", b, p))
- }
- }
- return res
-}
-
-func (pc *PerfConfig) ProcList(builder string) []int {
- ss := collectList(pc.BuilderProcs, 1, builder)
- var procs []int
- for _, s := range ss {
- p, _ := strconv.ParseInt(s, 10, 32)
- procs = append(procs, int(p))
- }
- sort.Ints(procs)
- return procs
-}
-
-// A PerfTodo contains outstanding commits for benchmarking for a builder.
-// Descendant of Package.
-type PerfTodo struct {
- PackagePath string // (empty for main repo commits)
- Builder string
- CommitNums []int `datastore:",noindex"` // LIFO queue of commits to benchmark.
-}
-
-func (todo *PerfTodo) Key(c appengine.Context) *datastore.Key {
- p := Package{Path: todo.PackagePath}
- key := todo.Builder
- return datastore.NewKey(c, "PerfTodo", key, 0, p.Key(c))
-}
-
-// AddCommitToPerfTodo adds the commit to all existing PerfTodo entities.
-func AddCommitToPerfTodo(c appengine.Context, com *Commit) error {
- var todos []*PerfTodo
- _, err := datastore.NewQuery("PerfTodo").
- Ancestor((&Package{}).Key(c)).
- GetAll(c, &todos)
- if err != nil {
- return fmt.Errorf("fetching PerfTodo's: %v", err)
- }
- for _, todo := range todos {
- todo.CommitNums = append(todo.CommitNums, com.Num)
- _, err = datastore.Put(c, todo.Key(c), todo)
- if err != nil {
- return fmt.Errorf("updating PerfTodo: %v", err)
- }
- }
- return nil
-}
-
-// A Log is a gzip-compressed log file stored under the SHA1 hash of the
-// uncompressed log text.
-type Log struct {
- CompressedLog []byte
-}
-
-func (l *Log) Text() ([]byte, error) {
- d, err := gzip.NewReader(bytes.NewBuffer(l.CompressedLog))
- if err != nil {
- return nil, fmt.Errorf("reading log data: %v", err)
- }
- b, err := ioutil.ReadAll(d)
- if err != nil {
- return nil, fmt.Errorf("reading log data: %v", err)
- }
- return b, nil
-}
-
-func PutLog(c appengine.Context, text string) (hash string, err error) {
- h := sha1.New()
- io.WriteString(h, text)
- b := new(bytes.Buffer)
- z, _ := gzip.NewWriterLevel(b, gzip.BestCompression)
- io.WriteString(z, text)
- z.Close()
- hash = fmt.Sprintf("%x", h.Sum(nil))
- key := datastore.NewKey(c, "Log", hash, 0, nil)
- _, err = datastore.Put(c, key, &Log{b.Bytes()})
- return
-}
-
-// A Tag is used to keep track of the most recent Go weekly and release tags.
-// Typically there will be one Tag entity for each kind of hg tag.
-type Tag struct {
- Kind string // "weekly", "release", or "tip"
- Name string // the tag itself (for example: "release.r60")
- Hash string
-}
-
-func (t *Tag) Key(c appengine.Context) *datastore.Key {
- p := &Package{}
- return datastore.NewKey(c, "Tag", t.Kind, 0, p.Key(c))
-}
-
-func (t *Tag) Valid() error {
- if t.Kind != "weekly" && t.Kind != "release" && t.Kind != "tip" {
- return errors.New("invalid Kind")
- }
- if !validHash(t.Hash) {
- return errors.New("invalid Hash")
- }
- return nil
-}
-
-// Commit returns the Commit that corresponds with this Tag.
-func (t *Tag) Commit(c appengine.Context) (*Commit, error) {
- com := &Commit{Hash: t.Hash}
- err := datastore.Get(c, com.Key(c), com)
- return com, err
-}
-
-// GetTag fetches a Tag by name from the datastore.
-func GetTag(c appengine.Context, tag string) (*Tag, error) {
- t := &Tag{Kind: tag}
- if err := datastore.Get(c, t.Key(c), t); err != nil {
- if err == datastore.ErrNoSuchEntity {
- return nil, errors.New("tag not found: " + tag)
- }
- return nil, err
- }
- if err := t.Valid(); err != nil {
- return nil, err
- }
- return t, nil
-}
-
-// Packages returns packages of the specified kind.
-// Kind must be one of "external" or "subrepo".
-func Packages(c appengine.Context, kind string) ([]*Package, error) {
- switch kind {
- case "external", "subrepo":
- default:
- return nil, errors.New(`kind must be one of "external" or "subrepo"`)
- }
- var pkgs []*Package
- q := datastore.NewQuery("Package").Filter("Kind=", kind)
- for t := q.Run(c); ; {
- pkg := new(Package)
- _, err := t.Next(pkg)
- if err == datastore.Done {
- break
- } else if err != nil {
- return nil, err
- }
- if pkg.Path != "" {
- pkgs = append(pkgs, pkg)
- }
- }
- return pkgs, nil
-}
diff --git a/dashboard/app/build/dash.go b/dashboard/app/build/dash.go
deleted file mode 100644
index 52ca74d..0000000
--- a/dashboard/app/build/dash.go
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-import (
- "net/http"
- "strings"
-
- "appengine"
-)
-
-// Dashboard describes a unique build dashboard.
-type Dashboard struct {
- Name string // This dashboard's name and namespace
- RelPath string // The relative url path
- Packages []*Package // The project's packages to build
-}
-
-// dashboardForRequest returns the appropriate dashboard for a given URL path.
-func dashboardForRequest(r *http.Request) *Dashboard {
- if strings.HasPrefix(r.URL.Path, gccgoDash.RelPath) {
- return gccgoDash
- }
- return goDash
-}
-
-// Context returns a namespaced context for this dashboard, or panics if it
-// fails to create a new context.
-func (d *Dashboard) Context(c appengine.Context) appengine.Context {
- // No namespace needed for the original Go dashboard.
- if d.Name == "Go" {
- return c
- }
- n, err := appengine.Namespace(c, d.Name)
- if err != nil {
- panic(err)
- }
- return n
-}
-
-// the currently known dashboards.
-var dashboards = []*Dashboard{goDash, gccgoDash}
-
-// goDash is the dashboard for the main go repository.
-var goDash = &Dashboard{
- Name: "Go",
- RelPath: "/",
- Packages: goPackages,
-}
-
-// goPackages is a list of all of the packages built by the main go repository.
-var goPackages = []*Package{
- {
- Kind: "go",
- Name: "Go",
- },
- {
- Kind: "subrepo",
- Name: "go.blog",
- Path: "code.google.com/p/go.blog",
- },
- {
- Kind: "subrepo",
- Name: "go.codereview",
- Path: "code.google.com/p/go.codereview",
- },
- {
- Kind: "subrepo",
- Name: "go.crypto",
- Path: "code.google.com/p/go.crypto",
- },
- {
- Kind: "subrepo",
- Name: "go.exp",
- Path: "code.google.com/p/go.exp",
- },
- {
- Kind: "subrepo",
- Name: "go.image",
- Path: "code.google.com/p/go.image",
- },
- {
- Kind: "subrepo",
- Name: "go.net",
- Path: "code.google.com/p/go.net",
- },
- {
- Kind: "subrepo",
- Name: "go.sys",
- Path: "code.google.com/p/go.sys",
- },
- {
- Kind: "subrepo",
- Name: "go.talks",
- Path: "code.google.com/p/go.talks",
- },
- {
- Kind: "subrepo",
- Name: "go.tools",
- Path: "code.google.com/p/go.tools",
- },
-}
-
-// gccgoDash is the dashboard for gccgo.
-var gccgoDash = &Dashboard{
- Name: "Gccgo",
- RelPath: "/gccgo/",
- Packages: []*Package{
- {
- Kind: "gccgo",
- Name: "Gccgo",
- },
- },
-}
diff --git a/dashboard/app/build/handler.go b/dashboard/app/build/handler.go
deleted file mode 100644
index 5d06815..0000000
--- a/dashboard/app/build/handler.go
+++ /dev/null
@@ -1,906 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-import (
- "bytes"
- "crypto/hmac"
- "crypto/md5"
- "encoding/json"
- "errors"
- "fmt"
- "io/ioutil"
- "net/http"
- "strconv"
- "strings"
- "unicode/utf8"
-
- "appengine"
- "appengine/datastore"
-
- "cache"
- "key"
-)
-
-const commitsPerPage = 30
-const watcherVersion = 2
-
-// commitHandler retrieves commit data or records a new commit.
-//
-// For GET requests it returns a Commit value for the specified
-// packagePath and hash.
-//
-// For POST requests it reads a JSON-encoded Commit value from the request
-// body and creates a new Commit entity. It also updates the "tip" Tag for
-// each new commit at tip.
-//
-// This handler is used by a gobuilder process in -commit mode.
-func commitHandler(r *http.Request) (interface{}, error) {
- c := contextForRequest(r)
- com := new(Commit)
-
- if r.Method == "GET" {
- com.PackagePath = r.FormValue("packagePath")
- com.Hash = r.FormValue("hash")
- err := datastore.Get(c, com.Key(c), com)
- if com.Num == 0 && com.Desc == "" {
- // Perf builder might have written an incomplete Commit.
- // Pretend it doesn't exist, so that we can get complete details.
- err = datastore.ErrNoSuchEntity
- }
- if err != nil {
- if err == datastore.ErrNoSuchEntity {
- // This error string is special.
- // The commit watcher expects it.
- // Do not change it.
- return nil, errors.New("Commit not found")
- }
- return nil, fmt.Errorf("getting Commit: %v", err)
- }
- if com.Num == 0 {
- // Corrupt state which shouldn't happen but does.
- // Return an error so builders' commit loops will
- // be willing to retry submitting this commit.
- return nil, errors.New("in datastore with zero Num")
- }
- if com.Desc == "" || com.User == "" {
- // Also shouldn't happen, but at least happened
- // once on a single commit when trying to fix data
- // in the datastore viewer UI?
- return nil, errors.New("missing field")
- }
- // Strip potentially large and unnecessary fields.
- com.ResultData = nil
- com.PerfResults = nil
- return com, nil
- }
- if r.Method != "POST" {
- return nil, errBadMethod(r.Method)
- }
- if !isMasterKey(c, r.FormValue("key")) {
- return nil, errors.New("can only POST commits with master key")
- }
-
- // For now, the commit watcher doesn't support gccgo,
- // so only do this check for Go commits.
- // TODO(adg,cmang): remove this check when gccgo is supported.
- if dashboardForRequest(r) == goDash {
- v, _ := strconv.Atoi(r.FormValue("version"))
- if v != watcherVersion {
- return nil, fmt.Errorf("rejecting POST from commit watcher; need version %v", watcherVersion)
- }
- }
-
- // POST request
- body, err := ioutil.ReadAll(r.Body)
- r.Body.Close()
- if err != nil {
- return nil, fmt.Errorf("reading Body: %v", err)
- }
- if !bytes.Contains(body, needsBenchmarkingBytes) {
- c.Warningf("old builder detected at %v", r.RemoteAddr)
- return nil, fmt.Errorf("rejecting old builder request, body does not contain %s: %q", needsBenchmarkingBytes, body)
- }
- if err := json.Unmarshal(body, com); err != nil {
- return nil, fmt.Errorf("unmarshaling body %q: %v", body, err)
- }
- com.Desc = limitStringLength(com.Desc, maxDatastoreStringLen)
- if err := com.Valid(); err != nil {
- return nil, fmt.Errorf("validating Commit: %v", err)
- }
- defer cache.Tick(c)
- tx := func(c appengine.Context) error {
- return addCommit(c, com)
- }
- return nil, datastore.RunInTransaction(c, tx, nil)
-}
-
-var needsBenchmarkingBytes = []byte(`"NeedsBenchmarking"`)
-
-// addCommit adds the Commit entity to the datastore and updates the tip Tag.
-// It must be run inside a datastore transaction.
-func addCommit(c appengine.Context, com *Commit) error {
- var ec Commit // existing commit
- isUpdate := false
- err := datastore.Get(c, com.Key(c), &ec)
- if err != nil && err != datastore.ErrNoSuchEntity {
- return fmt.Errorf("getting Commit: %v", err)
- }
- if err == nil {
- // Commit already in the datastore. Any fields different?
- // If not, don't do anything.
- changes := (com.Num != 0 && com.Num != ec.Num) ||
- com.ParentHash != ec.ParentHash ||
- com.Desc != ec.Desc ||
- com.User != ec.User ||
- !com.Time.Equal(ec.Time)
- if !changes {
- return nil
- }
- ec.ParentHash = com.ParentHash
- ec.Desc = com.Desc
- ec.User = com.User
- if !com.Time.IsZero() {
- ec.Time = com.Time
- }
- if com.Num != 0 {
- ec.Num = com.Num
- }
- isUpdate = true
- com = &ec
- }
- p, err := GetPackage(c, com.PackagePath)
- if err != nil {
- return fmt.Errorf("GetPackage: %v", err)
- }
- if com.Num == 0 {
- // get the next commit number
- com.Num = p.NextNum
- p.NextNum++
- if _, err := datastore.Put(c, p.Key(c), p); err != nil {
- return fmt.Errorf("putting Package: %v", err)
- }
- } else if com.Num >= p.NextNum {
- p.NextNum = com.Num + 1
- if _, err := datastore.Put(c, p.Key(c), p); err != nil {
- return fmt.Errorf("putting Package: %v", err)
- }
- }
- // if this isn't the first Commit test the parent commit exists.
- // The all zeros are returned by hg's p1node template for parentless commits.
- if com.ParentHash != "" && com.ParentHash != "0000000000000000000000000000000000000000" && com.ParentHash != "0000" {
- n, err := datastore.NewQuery("Commit").
- Filter("Hash =", com.ParentHash).
- Ancestor(p.Key(c)).
- Count(c)
- if err != nil {
- return fmt.Errorf("testing for parent Commit: %v", err)
- }
- if n == 0 {
- return errors.New("parent commit not found")
- }
- }
- // update the tip Tag if this is the Go repo and this isn't on a release branch
- if p.Path == "" && !strings.HasPrefix(com.Desc, "[") && !isUpdate {
- t := &Tag{Kind: "tip", Hash: com.Hash}
- if _, err = datastore.Put(c, t.Key(c), t); err != nil {
- return fmt.Errorf("putting Tag: %v", err)
- }
- }
- // put the Commit
- if err = putCommit(c, com); err != nil {
- return err
- }
- if com.NeedsBenchmarking {
- // add to CommitRun
- cr, err := GetCommitRun(c, com.Num)
- if err != nil {
- return err
- }
- if err = cr.AddCommit(c, com); err != nil {
- return err
- }
- // create PerfResult
- res := &PerfResult{CommitHash: com.Hash, CommitNum: com.Num}
- if _, err := datastore.Put(c, res.Key(c), res); err != nil {
- return fmt.Errorf("putting PerfResult: %v", err)
- }
- // Update perf todo if necessary.
- if err = AddCommitToPerfTodo(c, com); err != nil {
- return err
- }
- }
- return nil
-}
-
-// tagHandler records a new tag. It reads a JSON-encoded Tag value from the
-// request body and updates the Tag entity for the Kind of tag provided.
-//
-// This handler is used by a gobuilder process in -commit mode.
-func tagHandler(r *http.Request) (interface{}, error) {
- if r.Method != "POST" {
- return nil, errBadMethod(r.Method)
- }
-
- t := new(Tag)
- defer r.Body.Close()
- if err := json.NewDecoder(r.Body).Decode(t); err != nil {
- return nil, err
- }
- if err := t.Valid(); err != nil {
- return nil, err
- }
- c := contextForRequest(r)
- defer cache.Tick(c)
- _, err := datastore.Put(c, t.Key(c), t)
- return nil, err
-}
-
-// Todo is a todoHandler response.
-type Todo struct {
- Kind string // "build-go-commit" or "build-package"
- Data interface{}
-}
-
-// todoHandler returns the next action to be performed by a builder.
-// It expects "builder" and "kind" query parameters and returns a *Todo value.
-// Multiple "kind" parameters may be specified.
-func todoHandler(r *http.Request) (interface{}, error) {
- c := contextForRequest(r)
- now := cache.Now(c)
- key := "build-todo-" + r.Form.Encode()
- var todo *Todo
- if cache.Get(r, now, key, &todo) {
- return todo, nil
- }
- var err error
- builder := r.FormValue("builder")
- for _, kind := range r.Form["kind"] {
- var com *Commit
- switch kind {
- case "build-go-commit":
- com, err = buildTodo(c, builder, "", "")
- if com != nil {
- com.PerfResults = []string{}
- }
- case "build-package":
- packagePath := r.FormValue("packagePath")
- goHash := r.FormValue("goHash")
- com, err = buildTodo(c, builder, packagePath, goHash)
- if com != nil {
- com.PerfResults = []string{}
- }
- case "benchmark-go-commit":
- com, err = perfTodo(c, builder)
- }
- if com != nil || err != nil {
- if com != nil {
- // ResultData can be large and not needed on builder.
- com.ResultData = []string{}
- }
- todo = &Todo{Kind: kind, Data: com}
- break
- }
- }
- if err == nil {
- cache.Set(r, now, key, todo)
- }
- return todo, err
-}
-
-// buildTodo returns the next Commit to be built (or nil if none available).
-//
-// If packagePath and goHash are empty, it scans the first 20 Go Commits in
-// Num-descending order and returns the first one it finds that doesn't have a
-// Result for this builder.
-//
-// If provided with non-empty packagePath and goHash args, it scans the first
-// 20 Commits in Num-descending order for the specified packagePath and
-// returns the first that doesn't have a Result for this builder and goHash.
-func buildTodo(c appengine.Context, builder, packagePath, goHash string) (*Commit, error) {
- p, err := GetPackage(c, packagePath)
- if err != nil {
- return nil, err
- }
-
- t := datastore.NewQuery("Commit").
- Ancestor(p.Key(c)).
- Limit(commitsPerPage).
- Order("-Num").
- Run(c)
- for {
- com := new(Commit)
- if _, err := t.Next(com); err == datastore.Done {
- break
- } else if err != nil {
- return nil, err
- }
- if com.Result(builder, goHash) == nil {
- return com, nil
- }
- }
-
- // Nothing left to do if this is a package (not the Go tree).
- if packagePath != "" {
- return nil, nil
- }
-
- // If there are no Go tree commits left to build,
- // see if there are any subrepo commits that need to be built at tip.
- // If so, ask the builder to build a go tree at the tip commit.
- // TODO(adg): do the same for "weekly" and "release" tags.
-
- tag, err := GetTag(c, "tip")
- if err != nil {
- return nil, err
- }
-
- // Check that this Go commit builds OK for this builder.
- // If not, don't re-build as the subrepos will never get built anyway.
- com, err := tag.Commit(c)
- if err != nil {
- return nil, err
- }
- if r := com.Result(builder, ""); r != nil && !r.OK {
- return nil, nil
- }
-
- pkgs, err := Packages(c, "subrepo")
- if err != nil {
- return nil, err
- }
- for _, pkg := range pkgs {
- com, err := pkg.LastCommit(c)
- if err != nil {
- c.Warningf("%v: no Commit found: %v", pkg, err)
- continue
- }
- if com.Result(builder, tag.Hash) == nil {
- return tag.Commit(c)
- }
- }
-
- return nil, nil
-}
-
-// perfTodo returns the next Commit to be benchmarked (or nil if none available).
-func perfTodo(c appengine.Context, builder string) (*Commit, error) {
- p := &Package{}
- todo := &PerfTodo{Builder: builder}
- err := datastore.Get(c, todo.Key(c), todo)
- if err != nil && err != datastore.ErrNoSuchEntity {
- return nil, fmt.Errorf("fetching PerfTodo: %v", err)
- }
- if err == datastore.ErrNoSuchEntity {
- todo, err = buildPerfTodo(c, builder)
- if err != nil {
- return nil, err
- }
- }
- if len(todo.CommitNums) == 0 {
- return nil, nil
- }
-
- // Have commit to benchmark, fetch it.
- num := todo.CommitNums[len(todo.CommitNums)-1]
- t := datastore.NewQuery("Commit").
- Ancestor(p.Key(c)).
- Filter("Num =", num).
- Limit(1).
- Run(c)
- com := new(Commit)
- if _, err := t.Next(com); err != nil {
- return nil, err
- }
- if !com.NeedsBenchmarking {
- return nil, fmt.Errorf("commit from perf todo queue is not intended for benchmarking")
- }
-
- // Remove benchmarks from other builders.
- var benchs []string
- for _, b := range com.PerfResults {
- bb := strings.Split(b, "|")
- if bb[0] == builder && bb[1] != "meta-done" {
- benchs = append(benchs, bb[1])
- }
- }
- com.PerfResults = benchs
-
- return com, nil
-}
-
-// buildPerfTodo creates PerfTodo for the builder with all commits. In a transaction.
-func buildPerfTodo(c appengine.Context, builder string) (*PerfTodo, error) {
- todo := &PerfTodo{Builder: builder}
- tx := func(c appengine.Context) error {
- err := datastore.Get(c, todo.Key(c), todo)
- if err != nil && err != datastore.ErrNoSuchEntity {
- return fmt.Errorf("fetching PerfTodo: %v", err)
- }
- if err == nil {
- return nil
- }
- t := datastore.NewQuery("CommitRun").
- Ancestor((&Package{}).Key(c)).
- Order("-StartCommitNum").
- Run(c)
- var nums []int
- var releaseNums []int
- loop:
- for {
- cr := new(CommitRun)
- if _, err := t.Next(cr); err == datastore.Done {
- break
- } else if err != nil {
- return fmt.Errorf("scanning commit runs for perf todo: %v", err)
- }
- for i := len(cr.Hash) - 1; i >= 0; i-- {
- if !cr.NeedsBenchmarking[i] || cr.Hash[i] == "" {
- continue // There's nothing to see here. Move along.
- }
- num := cr.StartCommitNum + i
- for k, v := range knownTags {
- // Releases are benchmarked first, because they are important (and there are few of them).
- if cr.Hash[i] == v {
- releaseNums = append(releaseNums, num)
- if k == "go1" {
- break loop // Point of no benchmark: test/bench/shootout: update timing.log to Go 1.
- }
- }
- }
- nums = append(nums, num)
- }
- }
- todo.CommitNums = orderPerfTodo(nums)
- todo.CommitNums = append(todo.CommitNums, releaseNums...)
- if _, err = datastore.Put(c, todo.Key(c), todo); err != nil {
- return fmt.Errorf("putting PerfTodo: %v", err)
- }
- return nil
- }
- return todo, datastore.RunInTransaction(c, tx, nil)
-}
-
-func removeCommitFromPerfTodo(c appengine.Context, builder string, num int) error {
- todo := &PerfTodo{Builder: builder}
- err := datastore.Get(c, todo.Key(c), todo)
- if err != nil && err != datastore.ErrNoSuchEntity {
- return fmt.Errorf("fetching PerfTodo: %v", err)
- }
- if err == datastore.ErrNoSuchEntity {
- return nil
- }
- for i := len(todo.CommitNums) - 1; i >= 0; i-- {
- if todo.CommitNums[i] == num {
- for ; i < len(todo.CommitNums)-1; i++ {
- todo.CommitNums[i] = todo.CommitNums[i+1]
- }
- todo.CommitNums = todo.CommitNums[:i]
- _, err = datastore.Put(c, todo.Key(c), todo)
- if err != nil {
- return fmt.Errorf("putting PerfTodo: %v", err)
- }
- break
- }
- }
- return nil
-}
-
-// packagesHandler returns a list of the non-Go Packages monitored
-// by the dashboard.
-func packagesHandler(r *http.Request) (interface{}, error) {
- kind := r.FormValue("kind")
- c := contextForRequest(r)
- now := cache.Now(c)
- key := "build-packages-" + kind
- var p []*Package
- if cache.Get(r, now, key, &p) {
- return p, nil
- }
- p, err := Packages(c, kind)
- if err != nil {
- return nil, err
- }
- cache.Set(r, now, key, p)
- return p, nil
-}
-
-// resultHandler records a build result.
-// It reads a JSON-encoded Result value from the request body,
-// creates a new Result entity, and updates the relevant Commit entity.
-// If the Log field is not empty, resultHandler creates a new Log entity
-// and updates the LogHash field before putting the Commit entity.
-func resultHandler(r *http.Request) (interface{}, error) {
- if r.Method != "POST" {
- return nil, errBadMethod(r.Method)
- }
-
- c := contextForRequest(r)
- res := new(Result)
- defer r.Body.Close()
- if err := json.NewDecoder(r.Body).Decode(res); err != nil {
- return nil, fmt.Errorf("decoding Body: %v", err)
- }
- if err := res.Valid(); err != nil {
- return nil, fmt.Errorf("validating Result: %v", err)
- }
- defer cache.Tick(c)
- // store the Log text if supplied
- if len(res.Log) > 0 {
- hash, err := PutLog(c, res.Log)
- if err != nil {
- return nil, fmt.Errorf("putting Log: %v", err)
- }
- res.LogHash = hash
- }
- tx := func(c appengine.Context) error {
- // check Package exists
- if _, err := GetPackage(c, res.PackagePath); err != nil {
- return fmt.Errorf("GetPackage: %v", err)
- }
- // put Result
- if _, err := datastore.Put(c, res.Key(c), res); err != nil {
- return fmt.Errorf("putting Result: %v", err)
- }
- // add Result to Commit
- com := &Commit{PackagePath: res.PackagePath, Hash: res.Hash}
- if err := com.AddResult(c, res); err != nil {
- return fmt.Errorf("AddResult: %v", err)
- }
- // Send build failure notifications, if necessary.
- // Note this must run after the call AddResult, which
- // populates the Commit's ResultData field.
- return notifyOnFailure(c, com, res.Builder)
- }
- return nil, datastore.RunInTransaction(c, tx, nil)
-}
-
-// perf-result request payload
-type PerfRequest struct {
- Builder string
- Benchmark string
- Hash string
- OK bool
- Metrics []PerfMetric
- Artifacts []PerfArtifact
-}
-
-type PerfMetric struct {
- Type string
- Val uint64
-}
-
-type PerfArtifact struct {
- Type string
- Body string
-}
-
-// perfResultHandler records a becnhmarking result.
-func perfResultHandler(r *http.Request) (interface{}, error) {
- defer r.Body.Close()
- if r.Method != "POST" {
- return nil, errBadMethod(r.Method)
- }
-
- req := new(PerfRequest)
- if err := json.NewDecoder(r.Body).Decode(req); err != nil {
- return nil, fmt.Errorf("decoding Body: %v", err)
- }
-
- c := contextForRequest(r)
- defer cache.Tick(c)
-
- // store the text files if supplied
- for i, a := range req.Artifacts {
- hash, err := PutLog(c, a.Body)
- if err != nil {
- return nil, fmt.Errorf("putting Log: %v", err)
- }
- req.Artifacts[i].Body = hash
- }
- tx := func(c appengine.Context) error {
- return addPerfResult(c, r, req)
- }
- return nil, datastore.RunInTransaction(c, tx, nil)
-}
-
-// addPerfResult creates PerfResult and updates Commit, PerfTodo,
-// PerfMetricRun and PerfConfig.
-// MUST be called from inside a transaction.
-func addPerfResult(c appengine.Context, r *http.Request, req *PerfRequest) error {
- // check Package exists
- p, err := GetPackage(c, "")
- if err != nil {
- return fmt.Errorf("GetPackage: %v", err)
- }
- // add result to Commit
- com := &Commit{Hash: req.Hash}
- if err := com.AddPerfResult(c, req.Builder, req.Benchmark); err != nil {
- return fmt.Errorf("AddPerfResult: %v", err)
- }
-
- // add the result to PerfResult
- res := &PerfResult{CommitHash: req.Hash}
- if err := datastore.Get(c, res.Key(c), res); err != nil {
- return fmt.Errorf("getting PerfResult: %v", err)
- }
- present := res.AddResult(req)
- if _, err := datastore.Put(c, res.Key(c), res); err != nil {
- return fmt.Errorf("putting PerfResult: %v", err)
- }
-
- // Meta-done denotes that there are no benchmarks left.
- if req.Benchmark == "meta-done" {
- // Don't send duplicate emails for the same commit/builder.
- // And don't send emails about too old commits.
- if !present && com.Num >= p.NextNum-commitsPerPage {
- if err := checkPerfChanges(c, r, com, req.Builder, res); err != nil {
- return err
- }
- }
- if err := removeCommitFromPerfTodo(c, req.Builder, com.Num); err != nil {
- return nil
- }
- return nil
- }
-
- // update PerfConfig
- newBenchmark, err := UpdatePerfConfig(c, r, req)
- if err != nil {
- return fmt.Errorf("updating PerfConfig: %v", err)
- }
- if newBenchmark {
- // If this is a new benchmark on the builder, delete PerfTodo.
- // It will be recreated later with all commits again.
- todo := &PerfTodo{Builder: req.Builder}
- err = datastore.Delete(c, todo.Key(c))
- if err != nil && err != datastore.ErrNoSuchEntity {
- return fmt.Errorf("deleting PerfTodo: %v", err)
- }
- }
-
- // add perf metrics
- for _, metric := range req.Metrics {
- m, err := GetPerfMetricRun(c, req.Builder, req.Benchmark, metric.Type, com.Num)
- if err != nil {
- return fmt.Errorf("GetPerfMetrics: %v", err)
- }
- if err = m.AddMetric(c, com.Num, metric.Val); err != nil {
- return fmt.Errorf("AddMetric: %v", err)
- }
- }
-
- return nil
-}
-
-// MUST be called from inside a transaction.
-func checkPerfChanges(c appengine.Context, r *http.Request, com *Commit, builder string, res *PerfResult) error {
- pc, err := GetPerfConfig(c, r)
- if err != nil {
- return err
- }
-
- results := res.ParseData()[builder]
- rcNewer := MakePerfResultCache(c, com, true)
- rcOlder := MakePerfResultCache(c, com, false)
-
- // Check whether we need to send failure notification email.
- if results["meta-done"].OK {
- // This one is successful, see if the next is failed.
- nextRes, err := rcNewer.Next(com.Num)
- if err != nil {
- return err
- }
- if nextRes != nil && isPerfFailed(nextRes, builder) {
- sendPerfFailMail(c, builder, nextRes)
- }
- } else {
- // This one is failed, see if the previous is successful.
- prevRes, err := rcOlder.Next(com.Num)
- if err != nil {
- return err
- }
- if prevRes != nil && !isPerfFailed(prevRes, builder) {
- sendPerfFailMail(c, builder, res)
- }
- }
-
- // Now see if there are any performance changes.
- // Find the previous and the next results for performance comparison.
- prevRes, err := rcOlder.NextForComparison(com.Num, builder)
- if err != nil {
- return err
- }
- nextRes, err := rcNewer.NextForComparison(com.Num, builder)
- if err != nil {
- return err
- }
- if results["meta-done"].OK {
- // This one is successful, compare with a previous one.
- if prevRes != nil {
- if err := comparePerfResults(c, pc, builder, prevRes, res); err != nil {
- return err
- }
- }
- // Compare a next one with the current.
- if nextRes != nil {
- if err := comparePerfResults(c, pc, builder, res, nextRes); err != nil {
- return err
- }
- }
- } else {
- // This one is failed, compare a previous one with a next one.
- if prevRes != nil && nextRes != nil {
- if err := comparePerfResults(c, pc, builder, prevRes, nextRes); err != nil {
- return err
- }
- }
- }
-
- return nil
-}
-
-func comparePerfResults(c appengine.Context, pc *PerfConfig, builder string, prevRes, res *PerfResult) error {
- changes := significantPerfChanges(pc, builder, prevRes, res)
- if len(changes) == 0 {
- return nil
- }
- com := &Commit{Hash: res.CommitHash}
- if err := datastore.Get(c, com.Key(c), com); err != nil {
- return fmt.Errorf("getting commit %v: %v", com.Hash, err)
- }
- sendPerfMailLater.Call(c, com, prevRes.CommitHash, builder, changes) // add task to queue
- return nil
-}
-
-// logHandler displays log text for a given hash.
-// It handles paths like "/log/hash".
-func logHandler(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-type", "text/plain; charset=utf-8")
- c := contextForRequest(r)
- hash := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
- key := datastore.NewKey(c, "Log", hash, 0, nil)
- l := new(Log)
- if err := datastore.Get(c, key, l); err != nil {
- logErr(w, r, err)
- return
- }
- b, err := l.Text()
- if err != nil {
- logErr(w, r, err)
- return
- }
- w.Write(b)
-}
-
-type dashHandler func(*http.Request) (interface{}, error)
-
-type dashResponse struct {
- Response interface{}
- Error string
-}
-
-// errBadMethod is returned by a dashHandler when
-// the request has an unsuitable method.
-type errBadMethod string
-
-func (e errBadMethod) Error() string {
- return "bad method: " + string(e)
-}
-
-// AuthHandler wraps a http.HandlerFunc with a handler that validates the
-// supplied key and builder query parameters.
-func AuthHandler(h dashHandler) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- c := contextForRequest(r)
-
- // Put the URL Query values into r.Form to avoid parsing the
- // request body when calling r.FormValue.
- r.Form = r.URL.Query()
-
- var err error
- var resp interface{}
-
- // Validate key query parameter for POST requests only.
- key := r.FormValue("key")
- builder := r.FormValue("builder")
- if r.Method == "POST" && !validKey(c, key, builder) {
- err = fmt.Errorf("invalid key %q for builder %q", key, builder)
- }
-
- // Call the original HandlerFunc and return the response.
- if err == nil {
- resp, err = h(r)
- }
-
- // Write JSON response.
- dashResp := &dashResponse{Response: resp}
- if err != nil {
- c.Errorf("%v", err)
- dashResp.Error = err.Error()
- }
- w.Header().Set("Content-Type", "application/json")
- if err = json.NewEncoder(w).Encode(dashResp); err != nil {
- c.Criticalf("encoding response: %v", err)
- }
- }
-}
-
-func keyHandler(w http.ResponseWriter, r *http.Request) {
- builder := r.FormValue("builder")
- if builder == "" {
- logErr(w, r, errors.New("must supply builder in query string"))
- return
- }
- c := contextForRequest(r)
- fmt.Fprint(w, builderKey(c, builder))
-}
-
-func init() {
- for _, d := range dashboards {
- // admin handlers
- http.HandleFunc(d.RelPath+"init", initHandler)
- http.HandleFunc(d.RelPath+"key", keyHandler)
-
- // authenticated handlers
- http.HandleFunc(d.RelPath+"commit", AuthHandler(commitHandler))
- http.HandleFunc(d.RelPath+"packages", AuthHandler(packagesHandler))
- http.HandleFunc(d.RelPath+"result", AuthHandler(resultHandler))
- http.HandleFunc(d.RelPath+"perf-result", AuthHandler(perfResultHandler))
- http.HandleFunc(d.RelPath+"tag", AuthHandler(tagHandler))
- http.HandleFunc(d.RelPath+"todo", AuthHandler(todoHandler))
-
- // public handlers
- http.HandleFunc(d.RelPath+"log/", logHandler)
- }
-}
-
-func validHash(hash string) bool {
- // TODO(adg): correctly validate a hash
- return hash != ""
-}
-
-func validKey(c appengine.Context, key, builder string) bool {
- return isMasterKey(c, key) || key == builderKey(c, builder)
-}
-
-func isMasterKey(c appengine.Context, k string) bool {
- return appengine.IsDevAppServer() || k == key.Secret(c)
-}
-
-func builderKey(c appengine.Context, builder string) string {
- h := hmac.New(md5.New, []byte(key.Secret(c)))
- h.Write([]byte(builder))
- return fmt.Sprintf("%x", h.Sum(nil))
-}
-
-func logErr(w http.ResponseWriter, r *http.Request, err error) {
- contextForRequest(r).Errorf("Error: %v", err)
- w.WriteHeader(http.StatusInternalServerError)
- fmt.Fprint(w, "Error: ", err)
-}
-
-func contextForRequest(r *http.Request) appengine.Context {
- return dashboardForRequest(r).Context(appengine.NewContext(r))
-}
-
-// limitStringLength essentially does return s[:max],
-// but it ensures that we dot not split UTF-8 rune in half.
-// Otherwise appengine python scripts will break badly.
-func limitStringLength(s string, max int) string {
- if len(s) <= max {
- return s
- }
- for {
- s = s[:max]
- r, size := utf8.DecodeLastRuneInString(s)
- if r != utf8.RuneError || size != 1 {
- return s
- }
- max--
- }
-}
diff --git a/dashboard/app/build/init.go b/dashboard/app/build/init.go
deleted file mode 100644
index e7d63ed..0000000
--- a/dashboard/app/build/init.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2012 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-import (
- "fmt"
- "net/http"
-
- "appengine"
- "appengine/datastore"
-
- "cache"
- "key"
-)
-
-func initHandler(w http.ResponseWriter, r *http.Request) {
- d := dashboardForRequest(r)
- c := d.Context(appengine.NewContext(r))
- defer cache.Tick(c)
- for _, p := range d.Packages {
- err := datastore.Get(c, p.Key(c), new(Package))
- if _, ok := err.(*datastore.ErrFieldMismatch); ok {
- // Some fields have been removed, so it's okay to ignore this error.
- err = nil
- }
- if err == nil {
- continue
- } else if err != datastore.ErrNoSuchEntity {
- logErr(w, r, err)
- return
- }
- if _, err := datastore.Put(c, p.Key(c), p); err != nil {
- logErr(w, r, err)
- return
- }
- }
-
- // Create secret key.
- key.Secret(c)
-
- fmt.Fprint(w, "OK")
-}
diff --git a/dashboard/app/build/notify.go b/dashboard/app/build/notify.go
deleted file mode 100644
index 1a71dd2..0000000
--- a/dashboard/app/build/notify.go
+++ /dev/null
@@ -1,378 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-import (
- "bytes"
- "encoding/gob"
- "errors"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/url"
- "regexp"
- "runtime"
- "sort"
- "text/template"
-
- "appengine"
- "appengine/datastore"
- "appengine/delay"
- "appengine/mail"
- "appengine/urlfetch"
-)
-
-const (
- mailFrom = "builder@golang.org" // use this for sending any mail
- failMailTo = "golang-dev@googlegroups.com"
- domain = "build.golang.org"
- gobotBase = "http://research.swtch.com/gobot_codereview"
-)
-
-// ignoreFailure is a set of builders that we don't email about because
-// they are not yet production-ready.
-var ignoreFailure = map[string]bool{
- "dragonfly-386": true,
- "dragonfly-amd64": true,
- "freebsd-arm": true,
- "netbsd-amd64-bsiegert": true,
- "netbsd-arm-rpi": true,
- "plan9-amd64-aram": true,
-}
-
-// notifyOnFailure checks whether the supplied Commit or the subsequent
-// Commit (if present) breaks the build for this builder.
-// If either of those commits break the build an email notification is sent
-// from a delayed task. (We use a task because this way the mail won't be
-// sent if the enclosing datastore transaction fails.)
-//
-// This must be run in a datastore transaction, and the provided *Commit must
-// have been retrieved from the datastore within that transaction.
-func notifyOnFailure(c appengine.Context, com *Commit, builder string) error {
- if ignoreFailure[builder] {
- return nil
- }
-
- // TODO(adg): implement notifications for packages
- if com.PackagePath != "" {
- return nil
- }
-
- p := &Package{Path: com.PackagePath}
- var broken *Commit
- cr := com.Result(builder, "")
- if cr == nil {
- return fmt.Errorf("no result for %s/%s", com.Hash, builder)
- }
- q := datastore.NewQuery("Commit").Ancestor(p.Key(c))
- if cr.OK {
- // This commit is OK. Notify if next Commit is broken.
- next := new(Commit)
- q = q.Filter("ParentHash=", com.Hash)
- if err := firstMatch(c, q, next); err != nil {
- if err == datastore.ErrNoSuchEntity {
- // OK at tip, no notification necessary.
- return nil
- }
- return err
- }
- if nr := next.Result(builder, ""); nr != nil && !nr.OK {
- c.Debugf("commit ok: %#v\nresult: %#v", com, cr)
- c.Debugf("next commit broken: %#v\nnext result:%#v", next, nr)
- broken = next
- }
- } else {
- // This commit is broken. Notify if the previous Commit is OK.
- prev := new(Commit)
- q = q.Filter("Hash=", com.ParentHash)
- if err := firstMatch(c, q, prev); err != nil {
- if err == datastore.ErrNoSuchEntity {
- // No previous result, let the backfill of
- // this result trigger the notification.
- return nil
- }
- return err
- }
- if pr := prev.Result(builder, ""); pr != nil && pr.OK {
- c.Debugf("commit broken: %#v\nresult: %#v", com, cr)
- c.Debugf("previous commit ok: %#v\nprevious result:%#v", prev, pr)
- broken = com
- }
- }
- if broken == nil {
- return nil
- }
- r := broken.Result(builder, "")
- if r == nil {
- return fmt.Errorf("finding result for %q: %+v", builder, com)
- }
- return commonNotify(c, broken, builder, r.LogHash)
-}
-
-// firstMatch executes the query q and loads the first entity into v.
-func firstMatch(c appengine.Context, q *datastore.Query, v interface{}) error {
- t := q.Limit(1).Run(c)
- _, err := t.Next(v)
- if err == datastore.Done {
- err = datastore.ErrNoSuchEntity
- }
- return err
-}
-
-var notifyLater = delay.Func("notify", notify)
-
-// notify tries to update the CL for the given Commit with a failure message.
-// If it doesn't succeed, it sends a failure email to golang-dev.
-func notify(c appengine.Context, com *Commit, builder, logHash string) {
- v := url.Values{"brokebuild": {builder}, "log": {logHash}}
- if !updateCL(c, com, v) {
- // Send a mail notification if the CL can't be found.
- sendFailMail(c, com, builder, logHash)
- }
-}
-
-// updateCL tells gobot to update the CL for the given Commit with
-// the provided query values.
-func updateCL(c appengine.Context, com *Commit, v url.Values) bool {
- cl, err := lookupCL(c, com)
- if err != nil {
- c.Errorf("could not find CL for %v: %v", com.Hash, err)
- return false
- }
- u := fmt.Sprintf("%v?cl=%v&%s", gobotBase, cl, v.Encode())
- r, err := urlfetch.Client(c).Post(u, "text/plain", nil)
- if err != nil {
- c.Errorf("could not update CL %v: %v", cl, err)
- return false
- }
- r.Body.Close()
- if r.StatusCode != http.StatusOK {
- c.Errorf("could not update CL %v: %v", cl, r.Status)
- return false
- }
- return true
-}
-
-var clURL = regexp.MustCompile(`https://codereview.appspot.com/([0-9]+)`)
-
-// lookupCL consults code.google.com for the full change description for the
-// provided Commit, and returns the relevant CL number.
-func lookupCL(c appengine.Context, com *Commit) (string, error) {
- url := "https://code.google.com/p/go/source/detail?r=" + com.Hash
- r, err := urlfetch.Client(c).Get(url)
- if err != nil {
- return "", err
- }
- defer r.Body.Close()
- if r.StatusCode != http.StatusOK {
- return "", fmt.Errorf("retrieving %v: %v", url, r.Status)
- }
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return "", err
- }
- m := clURL.FindAllSubmatch(b, -1)
- if m == nil {
- return "", errors.New("no CL URL found on changeset page")
- }
- // Return the last visible codereview URL on the page,
- // in case the change description refers to another CL.
- return string(m[len(m)-1][1]), nil
-}
-
-var sendFailMailTmpl = template.Must(template.New("notify.txt").
- Funcs(template.FuncMap(tmplFuncs)).
- ParseFiles("build/notify.txt"))
-
-func init() {
- gob.Register(&Commit{}) // for delay
-}
-
-var (
- sendPerfMailLater = delay.Func("sendPerfMail", sendPerfMailFunc)
- sendPerfMailTmpl = template.Must(
- template.New("perf_notify.txt").
- Funcs(template.FuncMap(tmplFuncs)).
- ParseFiles("build/perf_notify.txt"),
- )
-)
-
-// MUST be called from inside a transaction.
-func sendPerfFailMail(c appengine.Context, builder string, res *PerfResult) error {
- com := &Commit{Hash: res.CommitHash}
- if err := datastore.Get(c, com.Key(c), com); err != nil {
- return err
- }
- logHash := ""
- parsed := res.ParseData()
- for _, data := range parsed[builder] {
- if !data.OK {
- logHash = data.Artifacts["log"]
- break
- }
- }
- if logHash == "" {
- return fmt.Errorf("can not find failed result for commit %v on builder %v", com.Hash, builder)
- }
- return commonNotify(c, com, builder, logHash)
-}
-
-// commonNotify MUST!!! be called from within a transaction inside which
-// the provided Commit entity was retrieved from the datastore.
-func commonNotify(c appengine.Context, com *Commit, builder, logHash string) error {
- if com.Num == 0 || com.Desc == "" {
- stk := make([]byte, 10000)
- n := runtime.Stack(stk, false)
- stk = stk[:n]
- c.Errorf("refusing to notify with com=%+v\n%s", *com, string(stk))
- return fmt.Errorf("misuse of commonNotify")
- }
- if com.FailNotificationSent {
- return nil
- }
- c.Infof("%s is broken commit; notifying", com.Hash)
- notifyLater.Call(c, com, builder, logHash) // add task to queue
- com.FailNotificationSent = true
- return putCommit(c, com)
-}
-
-// sendFailMail sends a mail notification that the build failed on the
-// provided commit and builder.
-func sendFailMail(c appengine.Context, com *Commit, builder, logHash string) {
- // get Log
- k := datastore.NewKey(c, "Log", logHash, 0, nil)
- l := new(Log)
- if err := datastore.Get(c, k, l); err != nil {
- c.Errorf("finding Log record %v: %v", logHash, err)
- return
- }
- logText, err := l.Text()
- if err != nil {
- c.Errorf("unpacking Log record %v: %v", logHash, err)
- return
- }
-
- // prepare mail message
- var body bytes.Buffer
- err = sendFailMailTmpl.Execute(&body, map[string]interface{}{
- "Builder": builder, "Commit": com, "LogHash": logHash, "LogText": logText,
- "Hostname": domain,
- })
- if err != nil {
- c.Errorf("rendering mail template: %v", err)
- return
- }
- subject := fmt.Sprintf("%s broken by %s", builder, shortDesc(com.Desc))
- msg := &mail.Message{
- Sender: mailFrom,
- To: []string{failMailTo},
- ReplyTo: failMailTo,
- Subject: subject,
- Body: body.String(),
- }
-
- // send mail
- if err := mail.Send(c, msg); err != nil {
- c.Errorf("sending mail: %v", err)
- }
-}
-
-type PerfChangeBenchmark struct {
- Name string
- Metrics []*PerfChangeMetric
-}
-
-type PerfChangeMetric struct {
- Name string
- Old uint64
- New uint64
- Delta float64
-}
-
-type PerfChangeBenchmarkSlice []*PerfChangeBenchmark
-
-func (l PerfChangeBenchmarkSlice) Len() int { return len(l) }
-func (l PerfChangeBenchmarkSlice) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
-func (l PerfChangeBenchmarkSlice) Less(i, j int) bool {
- b1, p1 := splitBench(l[i].Name)
- b2, p2 := splitBench(l[j].Name)
- if b1 != b2 {
- return b1 < b2
- }
- return p1 < p2
-}
-
-type PerfChangeMetricSlice []*PerfChangeMetric
-
-func (l PerfChangeMetricSlice) Len() int { return len(l) }
-func (l PerfChangeMetricSlice) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
-func (l PerfChangeMetricSlice) Less(i, j int) bool { return l[i].Name < l[j].Name }
-
-func sendPerfMailFunc(c appengine.Context, com *Commit, prevCommitHash, builder string, changes []*PerfChange) {
- // Sort the changes into the right order.
- var benchmarks []*PerfChangeBenchmark
- for _, ch := range changes {
- // Find the benchmark.
- var b *PerfChangeBenchmark
- for _, b1 := range benchmarks {
- if b1.Name == ch.Bench {
- b = b1
- break
- }
- }
- if b == nil {
- b = &PerfChangeBenchmark{Name: ch.Bench}
- benchmarks = append(benchmarks, b)
- }
- b.Metrics = append(b.Metrics, &PerfChangeMetric{Name: ch.Metric, Old: ch.Old, New: ch.New, Delta: ch.Diff})
- }
- for _, b := range benchmarks {
- sort.Sort(PerfChangeMetricSlice(b.Metrics))
- }
- sort.Sort(PerfChangeBenchmarkSlice(benchmarks))
-
- u := fmt.Sprintf("http://%v/perfdetail?commit=%v&commit0=%v&kind=builder&builder=%v", domain, com.Hash, prevCommitHash, builder)
-
- // Prepare mail message (without Commit, for updateCL).
- var body bytes.Buffer
- err := sendPerfMailTmpl.Execute(&body, map[string]interface{}{
- "Builder": builder, "Hostname": domain, "Url": u, "Benchmarks": benchmarks,
- })
- if err != nil {
- c.Errorf("rendering perf mail template: %v", err)
- return
- }
-
- // First, try to update the CL.
- v := url.Values{"textmsg": {body.String()}}
- if updateCL(c, com, v) {
- return
- }
-
- // Otherwise, send mail (with Commit, for independent mail message).
- body.Reset()
- err = sendPerfMailTmpl.Execute(&body, map[string]interface{}{
- "Builder": builder, "Commit": com, "Hostname": domain, "Url": u, "Benchmarks": benchmarks,
- })
- if err != nil {
- c.Errorf("rendering perf mail template: %v", err)
- return
- }
- subject := fmt.Sprintf("Perf changes on %s by %s", builder, shortDesc(com.Desc))
- msg := &mail.Message{
- Sender: mailFrom,
- To: []string{failMailTo},
- ReplyTo: failMailTo,
- Subject: subject,
- Body: body.String(),
- }
-
- // send mail
- if err := mail.Send(c, msg); err != nil {
- c.Errorf("sending mail: %v", err)
- }
-}
diff --git a/dashboard/app/build/notify.txt b/dashboard/app/build/notify.txt
deleted file mode 100644
index 514191f..0000000
--- a/dashboard/app/build/notify.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-Change {{shortHash .Commit.Hash}} broke the {{.Builder}} build:
-http://{{.Hostname}}/log/{{.LogHash}}
-
-{{.Commit.Desc}}
-
-http://code.google.com/p/go/source/detail?r={{shortHash .Commit.Hash}}
-
-$ tail -200 < log
-{{printf "%s" .LogText | tail 200}}
diff --git a/dashboard/app/build/perf.go b/dashboard/app/build/perf.go
deleted file mode 100644
index 2c16e60..0000000
--- a/dashboard/app/build/perf.go
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-import (
- "fmt"
- "sort"
- "strconv"
- "strings"
-
- "appengine"
- "appengine/datastore"
-)
-
-var knownTags = map[string]string{
- "go1": "0051c7442fed9c888de6617fa9239a913904d96e",
- "go1.1": "d29da2ced72ba2cf48ed6a8f1ec4abc01e4c5bf1",
- "go1.2": "b1edf8faa5d6cbc50c6515785df9df9c19296564",
- "go1.3": "f153208c0a0e306bfca14f71ef11f09859ccabc8",
-}
-
-var lastRelease = "go1.3"
-
-func splitBench(benchProcs string) (string, int) {
- ss := strings.Split(benchProcs, "-")
- procs, _ := strconv.Atoi(ss[1])
- return ss[0], procs
-}
-
-func dashPerfCommits(c appengine.Context, page int) ([]*Commit, error) {
- q := datastore.NewQuery("Commit").
- Ancestor((&Package{}).Key(c)).
- Order("-Num").
- Filter("NeedsBenchmarking =", true).
- Limit(commitsPerPage).
- Offset(page * commitsPerPage)
- var commits []*Commit
- _, err := q.GetAll(c, &commits)
- if err == nil && len(commits) == 0 {
- err = fmt.Errorf("no commits")
- }
- return commits, err
-}
-
-func perfChangeStyle(pc *PerfConfig, v float64, builder, benchmark, metric string) string {
- noise := pc.NoiseLevel(builder, benchmark, metric)
- if isNoise(v, noise) {
- return "noise"
- }
- if v > 0 {
- return "bad"
- }
- return "good"
-}
-
-func isNoise(diff, noise float64) bool {
- rnoise := -100 * noise / (noise + 100)
- return diff < noise && diff > rnoise
-}
-
-func perfDiff(old, new uint64) float64 {
- return 100*float64(new)/float64(old) - 100
-}
-
-func isPerfFailed(res *PerfResult, builder string) bool {
- data := res.ParseData()[builder]
- return data != nil && data["meta-done"] != nil && !data["meta-done"].OK
-}
-
-// PerfResultCache caches a set of PerfResults so that it's easy to access them
-// without lots of duplicate accesses to datastore.
-// It allows to iterate over newer or older results for some base commit.
-type PerfResultCache struct {
- c appengine.Context
- newer bool
- iter *datastore.Iterator
- results map[int]*PerfResult
-}
-
-func MakePerfResultCache(c appengine.Context, com *Commit, newer bool) *PerfResultCache {
- p := &Package{}
- q := datastore.NewQuery("PerfResult").Ancestor(p.Key(c)).Limit(100)
- if newer {
- q = q.Filter("CommitNum >=", com.Num).Order("CommitNum")
- } else {
- q = q.Filter("CommitNum <=", com.Num).Order("-CommitNum")
- }
- rc := &PerfResultCache{c: c, newer: newer, iter: q.Run(c), results: make(map[int]*PerfResult)}
- return rc
-}
-
-func (rc *PerfResultCache) Get(commitNum int) *PerfResult {
- rc.Next(commitNum) // fetch the commit, if necessary
- return rc.results[commitNum]
-}
-
-// Next returns the next PerfResult for the commit commitNum.
-// It does not care whether the result has any data, failed or whatever.
-func (rc *PerfResultCache) Next(commitNum int) (*PerfResult, error) {
- // See if we have next result in the cache.
- next := -1
- for ci := range rc.results {
- if rc.newer {
- if ci > commitNum && (next == -1 || ci < next) {
- next = ci
- }
- } else {
- if ci < commitNum && (next == -1 || ci > next) {
- next = ci
- }
- }
- }
- if next != -1 {
- return rc.results[next], nil
- }
- // Fetch next result from datastore.
- res := new(PerfResult)
- _, err := rc.iter.Next(res)
- if err == datastore.Done {
- return nil, nil
- }
- if err != nil {
- return nil, fmt.Errorf("fetching perf results: %v", err)
- }
- if (rc.newer && res.CommitNum < commitNum) || (!rc.newer && res.CommitNum > commitNum) {
- rc.c.Errorf("PerfResultCache.Next: bad commit num")
- }
- rc.results[res.CommitNum] = res
- return res, nil
-}
-
-// NextForComparison returns PerfResult which we need to use for performance comprison.
-// It skips failed results, but does not skip results with no data.
-func (rc *PerfResultCache) NextForComparison(commitNum int, builder string) (*PerfResult, error) {
- for {
- res, err := rc.Next(commitNum)
- if err != nil {
- return nil, err
- }
- if res == nil {
- return nil, nil
- }
- if res.CommitNum == commitNum {
- continue
- }
- parsed := res.ParseData()
- if builder != "" {
- // Comparing for a particular builder.
- // This is used in perf_changes and in email notifications.
- b := parsed[builder]
- if b == nil || b["meta-done"] == nil {
- // No results yet, must not do the comparison.
- return nil, nil
- }
- if b["meta-done"].OK {
- // Have complete results, compare.
- return res, nil
- }
- } else {
- // Comparing for all builders, find a result with at least
- // one successful meta-done.
- // This is used in perf_detail.
- for _, benchs := range parsed {
- if data := benchs["meta-done"]; data != nil && data.OK {
- return res, nil
- }
- }
- }
- // Failed, try next result.
- commitNum = res.CommitNum
- }
-}
-
-type PerfChange struct {
- Builder string
- Bench string
- Metric string
- Old uint64
- New uint64
- Diff float64
-}
-
-func significantPerfChanges(pc *PerfConfig, builder string, prevRes, res *PerfResult) (changes []*PerfChange) {
- // First, collect all significant changes.
- for builder1, benchmarks1 := range res.ParseData() {
- if builder != "" && builder != builder1 {
- // This is not the builder you're looking for, Luke.
- continue
- }
- benchmarks0 := prevRes.ParseData()[builder1]
- if benchmarks0 == nil {
- continue
- }
- for benchmark, data1 := range benchmarks1 {
- data0 := benchmarks0[benchmark]
- if data0 == nil {
- continue
- }
- for metric, val := range data1.Metrics {
- val0 := data0.Metrics[metric]
- if val0 == 0 {
- continue
- }
- diff := perfDiff(val0, val)
- noise := pc.NoiseLevel(builder, benchmark, metric)
- if isNoise(diff, noise) {
- continue
- }
- ch := &PerfChange{Builder: builder, Bench: benchmark, Metric: metric, Old: val0, New: val, Diff: diff}
- changes = append(changes, ch)
- }
- }
- }
- // Then, strip non-repeatable changes (flakes).
- // The hypothesis is that a real change must show up with the majority of GOMAXPROCS values.
- majority := len(pc.ProcList(builder))/2 + 1
- cnt := make(map[string]int)
- for _, ch := range changes {
- b, _ := splitBench(ch.Bench)
- name := b + "|" + ch.Metric
- if ch.Diff < 0 {
- name += "--"
- }
- cnt[name] = cnt[name] + 1
- }
- for i := 0; i < len(changes); i++ {
- ch := changes[i]
- b, _ := splitBench(ch.Bench)
- name := b + "|" + ch.Metric
- if cnt[name] >= majority {
- continue
- }
- if cnt[name+"--"] >= majority {
- continue
- }
- // Remove flake.
- last := len(changes) - 1
- changes[i] = changes[last]
- changes = changes[:last]
- i--
- }
- return changes
-}
-
-// orderPerfTodo reorders commit nums for benchmarking todo.
-// The resulting order is somewhat tricky. We want 2 things:
-// 1. benchmark sequentially backwards (this provides information about most
-// recent changes, and allows to estimate noise levels)
-// 2. benchmark old commits in "scatter" order (this allows to quickly gather
-// brief information about thousands of old commits)
-// So this function interleaves the two orders.
-func orderPerfTodo(nums []int) []int {
- sort.Ints(nums)
- n := len(nums)
- pow2 := uint32(0) // next power-of-two that is >= n
- npow2 := 0
- for npow2 <= n {
- pow2++
- npow2 = 1 << pow2
- }
- res := make([]int, n)
- resPos := n - 1 // result array is filled backwards
- present := make([]bool, n) // denotes values that already present in result array
- for i0, i1 := n-1, 0; i0 >= 0 || i1 < npow2; {
- // i0 represents "benchmark sequentially backwards" sequence
- // find the next commit that is not yet present and add it
- for cnt := 0; cnt < 2; cnt++ {
- for ; i0 >= 0; i0-- {
- if !present[i0] {
- present[i0] = true
- res[resPos] = nums[i0]
- resPos--
- i0--
- break
- }
- }
- }
- // i1 represents "scatter order" sequence
- // find the next commit that is not yet present and add it
- for ; i1 < npow2; i1++ {
- // do the "recursive split-ordering" trick
- idx := 0 // bitwise reverse of i1
- for j := uint32(0); j <= pow2; j++ {
- if (i1 & (1 << j)) != 0 {
- idx = idx | (1 << (pow2 - j - 1))
- }
- }
- if idx < n && !present[idx] {
- present[idx] = true
- res[resPos] = nums[idx]
- resPos--
- i1++
- break
- }
- }
- }
- // The above can't possibly be correct. Do dump check.
- res2 := make([]int, n)
- copy(res2, res)
- sort.Ints(res2)
- for i := range res2 {
- if res2[i] != nums[i] {
- panic(fmt.Sprintf("diff at %v: expect %v, want %v\nwas: %v\n become: %v",
- i, nums[i], res2[i], nums, res2))
- }
- }
- return res
-}
diff --git a/dashboard/app/build/perf_changes.go b/dashboard/app/build/perf_changes.go
deleted file mode 100644
index 4abbf1a..0000000
--- a/dashboard/app/build/perf_changes.go
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-import (
- "bytes"
- "fmt"
- "html/template"
- "net/http"
- "sort"
- "strconv"
-
- "appengine"
- "appengine/datastore"
-)
-
-func init() {
- http.HandleFunc("/perf", perfChangesHandler)
-}
-
-// perfSummaryHandler draws the main benchmarking page.
-func perfChangesHandler(w http.ResponseWriter, r *http.Request) {
- d := dashboardForRequest(r)
- c := d.Context(appengine.NewContext(r))
-
- page, _ := strconv.Atoi(r.FormValue("page"))
- if page < 0 {
- page = 0
- }
-
- pc, err := GetPerfConfig(c, r)
- if err != nil {
- logErr(w, r, err)
- return
- }
-
- commits, err := dashPerfCommits(c, page)
- if err != nil {
- logErr(w, r, err)
- return
- }
-
- // Fetch PerfResult's for the commits.
- var uiCommits []*perfChangesCommit
- rc := MakePerfResultCache(c, commits[0], false)
-
- // But first compare tip with the last release.
- if page == 0 {
- res0 := &PerfResult{CommitHash: knownTags[lastRelease]}
- if err := datastore.Get(c, res0.Key(c), res0); err != nil && err != datastore.ErrNoSuchEntity {
- logErr(w, r, fmt.Errorf("getting PerfResult: %v", err))
- return
- }
- if err != datastore.ErrNoSuchEntity {
- uiCom, err := handleOneCommit(pc, commits[0], rc, res0)
- if err != nil {
- logErr(w, r, err)
- return
- }
- uiCom.IsSummary = true
- uiCom.ParentHash = lastRelease
- uiCommits = append(uiCommits, uiCom)
- }
- }
-
- for _, com := range commits {
- uiCom, err := handleOneCommit(pc, com, rc, nil)
- if err != nil {
- logErr(w, r, err)
- return
- }
- uiCommits = append(uiCommits, uiCom)
- }
-
- p := &Pagination{}
- if len(commits) == commitsPerPage {
- p.Next = page + 1
- }
- if page > 0 {
- p.Prev = page - 1
- p.HasPrev = true
- }
-
- data := &perfChangesData{d, p, uiCommits}
-
- var buf bytes.Buffer
- if err := perfChangesTemplate.Execute(&buf, data); err != nil {
- logErr(w, r, err)
- return
- }
-
- buf.WriteTo(w)
-}
-
-func handleOneCommit(pc *PerfConfig, com *Commit, rc *PerfResultCache, baseRes *PerfResult) (*perfChangesCommit, error) {
- uiCom := new(perfChangesCommit)
- uiCom.Commit = com
- res1 := rc.Get(com.Num)
- for builder, benchmarks1 := range res1.ParseData() {
- for benchmark, data1 := range benchmarks1 {
- if benchmark != "meta-done" || !data1.OK {
- uiCom.NumResults++
- }
- if !data1.OK {
- v := new(perfChangesChange)
- v.diff = 10000
- v.Style = "fail"
- v.Builder = builder
- v.Link = fmt.Sprintf("log/%v", data1.Artifacts["log"])
- v.Val = builder
- v.Hint = builder
- if benchmark != "meta-done" {
- v.Hint += "/" + benchmark
- }
- m := findMetric(uiCom, "failure")
- m.BadChanges = append(m.BadChanges, v)
- }
- }
- res0 := baseRes
- if res0 == nil {
- var err error
- res0, err = rc.NextForComparison(com.Num, builder)
- if err != nil {
- return nil, err
- }
- if res0 == nil {
- continue
- }
- }
- changes := significantPerfChanges(pc, builder, res0, res1)
- changes = dedupPerfChanges(changes)
- for _, ch := range changes {
- v := new(perfChangesChange)
- v.Builder = builder
- v.Benchmark, v.Procs = splitBench(ch.Bench)
- v.diff = ch.Diff
- v.Val = fmt.Sprintf("%+.2f%%", ch.Diff)
- v.Hint = fmt.Sprintf("%v/%v", builder, ch.Bench)
- v.Link = fmt.Sprintf("perfdetail?commit=%v&commit0=%v&builder=%v&benchmark=%v", com.Hash, res0.CommitHash, builder, v.Benchmark)
- m := findMetric(uiCom, ch.Metric)
- if v.diff > 0 {
- v.Style = "bad"
- m.BadChanges = append(m.BadChanges, v)
- } else {
- v.Style = "good"
- m.GoodChanges = append(m.GoodChanges, v)
- }
- }
- }
-
- // Sort metrics and changes.
- for _, m := range uiCom.Metrics {
- sort.Sort(m.GoodChanges)
- sort.Sort(m.BadChanges)
- }
- sort.Sort(uiCom.Metrics)
- // Need at least one metric for UI.
- if len(uiCom.Metrics) == 0 {
- uiCom.Metrics = append(uiCom.Metrics, &perfChangesMetric{})
- }
- uiCom.Metrics[0].First = true
- return uiCom, nil
-}
-
-// Find builder-procs with the maximum absolute diff for every benchmark-metric, drop the rest.
-func dedupPerfChanges(changes []*PerfChange) (deduped []*PerfChange) {
- maxDiff := make(map[string]float64)
- maxBench := make(map[string]string)
- // First, find the maximum.
- for _, ch := range changes {
- bench, _ := splitBench(ch.Bench)
- k := bench + "|" + ch.Metric
- v := ch.Diff
- if v < 0 {
- v = -v
- }
- if maxDiff[k] < v {
- maxDiff[k] = v
- maxBench[k] = ch.Builder + "|" + ch.Bench
- }
- }
- // Then, remove the rest.
- for _, ch := range changes {
- bench, _ := splitBench(ch.Bench)
- k := bench + "|" + ch.Metric
- if maxBench[k] == ch.Builder+"|"+ch.Bench {
- deduped = append(deduped, ch)
- }
- }
- return
-}
-
-func findMetric(c *perfChangesCommit, metric string) *perfChangesMetric {
- for _, m := range c.Metrics {
- if m.Name == metric {
- return m
- }
- }
- m := new(perfChangesMetric)
- m.Name = metric
- c.Metrics = append(c.Metrics, m)
- return m
-}
-
-type uiPerfConfig struct {
- Builders []uiPerfConfigElem
- Benchmarks []uiPerfConfigElem
- Metrics []uiPerfConfigElem
- Procs []uiPerfConfigElem
- CommitsFrom []uiPerfConfigElem
- CommitsTo []uiPerfConfigElem
-}
-
-type uiPerfConfigElem struct {
- Name string
- Selected bool
-}
-
-var perfChangesTemplate = template.Must(
- template.New("perf_changes.html").Funcs(tmplFuncs).ParseFiles("build/perf_changes.html"),
-)
-
-type perfChangesData struct {
- Dashboard *Dashboard
- Pagination *Pagination
- Commits []*perfChangesCommit
-}
-
-type perfChangesCommit struct {
- *Commit
- IsSummary bool
- NumResults int
- Metrics perfChangesMetricSlice
-}
-
-type perfChangesMetric struct {
- Name string
- First bool
- BadChanges perfChangesChangeSlice
- GoodChanges perfChangesChangeSlice
-}
-
-type perfChangesChange struct {
- Builder string
- Benchmark string
- Link string
- Hint string
- Style string
- Val string
- Procs int
- diff float64
-}
-
-type perfChangesMetricSlice []*perfChangesMetric
-
-func (l perfChangesMetricSlice) Len() int { return len(l) }
-func (l perfChangesMetricSlice) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
-func (l perfChangesMetricSlice) Less(i, j int) bool {
- if l[i].Name == "failure" || l[j].Name == "failure" {
- return l[i].Name == "failure"
- }
- return l[i].Name < l[j].Name
-}
-
-type perfChangesChangeSlice []*perfChangesChange
-
-func (l perfChangesChangeSlice) Len() int { return len(l) }
-func (l perfChangesChangeSlice) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
-func (l perfChangesChangeSlice) Less(i, j int) bool {
- vi, vj := l[i].diff, l[j].diff
- if vi > 0 && vj > 0 {
- return vi > vj
- } else if vi < 0 && vj < 0 {
- return vi < vj
- } else {
- panic("comparing positive and negative diff")
- }
-}
diff --git a/dashboard/app/build/perf_changes.html b/dashboard/app/build/perf_changes.html
deleted file mode 100644
index 24f0534..0000000
--- a/dashboard/app/build/perf_changes.html
+++ /dev/null
@@ -1,89 +0,0 @@
-<!doctype html>
-<html>
-<head>
- <title>{{$.Dashboard.Name}} Dashboard</title>
- <link rel="stylesheet" href="/static/style.css"/>
-</head>
-<body>
- <header id="topbar">
- <h1>Go Dashboard</h1>
- <nav>
- <a href="{{$.Dashboard.RelPath}}">Test</a>
- <a href="{{$.Dashboard.RelPath}}perf">Perf</a>
- <a href="{{$.Dashboard.RelPath}}perfgraph">Graphs</a>
- </nav>
- <div class="clear"></div>
- </header>
-
- <div class="page">
- <div class="build-container">
- <table class="build">
- <colgroup class="col-hash"></colgroup>
- <colgroup class="col-numresults"></colgroup>
- <colgroup class="col-metric"></colgroup>
- <colgroup class="col-result"></colgroup>
- <colgroup class="col-result"></colgroup>
- <colgroup class="col-user"></colgroup>
- <colgroup class="col-time"></colgroup>
- <colgroup class="col-desc"></colgroup>
- <tbody>
- {{range $c := $.Commits}}
- {{range $m := $c.Metrics}}
- {{if $m.First}}
- <tr class="row-commit">
- {{if $c.IsSummary}}
- <td class="hash">tip vs {{$c.ParentHash}}</td>
- {{else}}
- <td class="hash"><a href="{{repoURL $.Dashboard.Name $c.Hash ""}}">{{shortHash $c.Hash}}</a></td>
- {{end}}
- <td class="numresults">{{$c.NumResults}}</td>
- {{else}}
- <tr>
- <td class="user">&nbsp;</td>
- <td class="numresults">&nbsp;</td>
- {{end}}
- <td>{{$m.Name}}</td>
- <td>
- {{range $ch := $m.BadChanges}}
- <a class="{{$ch.Style}}" href="{{$ch.Link}}" title="{{$ch.Hint}}">{{$ch.Val}}</a> &nbsp;
- {{end}}
- </td>
- <td>
- {{range $ch := $m.GoodChanges}}
- <a class="{{$ch.Style}}" href="{{$ch.Link}}" title="{{$ch.Hint}}">{{$ch.Val}}</a> &nbsp;
- {{end}}
- </td>
- {{if $m.First}}
- <td class="user" title="{{$c.User}}">{{shortUser $c.User}}</td>
- <td class="time">{{$c.Time.Format "Mon 02 Jan 15:04"}}</td>
- <td class="desc" title="{{$c.Desc}}">{{shortDesc $c.Desc}}</td>
- {{else}}
- <td class="user">&nbsp;</td>
- <td class="time">&nbsp;</td>
- <td class="desc">&nbsp;</td>
- {{end}}
- </tr>
- {{end}}
- {{if $c.IsSummary}}
- <tr class="row-commit"><td>---</td></tr>
- {{end}}
- {{end}}
- </tbody>
- </table>
-
- {{with $.Pagination}}
- <div class="paginate">
- <nav>
- <a {{if .HasPrev}}href="?page={{.Prev}}"{{else}}class="inactive"{{end}}>newer</a>
- <a {{if .Next}}href="?page={{.Next}}"{{else}}class="inactive"{{end}}>older</a>
- <a {{if .HasPrev}}href="?"{{else}}class="inactive"{{end}}>latest</a>
- <a href="https://code.google.com/p/go-wiki/wiki/PerfDashboard">Help</a>
- </nav>
- </div>
- {{end}}
-
- </div>
- <div class="clear"></div>
-</div>
-</body>
-</html>
diff --git a/dashboard/app/build/perf_detail.go b/dashboard/app/build/perf_detail.go
deleted file mode 100644
index f8d9bfd..0000000
--- a/dashboard/app/build/perf_detail.go
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-import (
- "bytes"
- "fmt"
- "html/template"
- "net/http"
- "sort"
- "strconv"
- "strings"
-
- "appengine"
- "appengine/datastore"
-)
-
-func init() {
- for _, d := range dashboards {
- http.HandleFunc(d.RelPath+"perfdetail", perfDetailUIHandler)
- }
-}
-
-func perfDetailUIHandler(w http.ResponseWriter, r *http.Request) {
- d := dashboardForRequest(r)
- c := d.Context(appengine.NewContext(r))
- pc, err := GetPerfConfig(c, r)
- if err != nil {
- logErr(w, r, err)
- return
- }
-
- kind := r.FormValue("kind")
- builder := r.FormValue("builder")
- benchmark := r.FormValue("benchmark")
- if kind == "" {
- kind = "benchmark"
- }
- if kind != "benchmark" && kind != "builder" {
- logErr(w, r, fmt.Errorf("unknown kind %s", kind))
- return
- }
-
- // Fetch the new commit.
- com1 := new(Commit)
- com1.Hash = r.FormValue("commit")
- if hash, ok := knownTags[com1.Hash]; ok {
- com1.Hash = hash
- }
- if err := datastore.Get(c, com1.Key(c), com1); err != nil {
- logErr(w, r, fmt.Errorf("failed to fetch commit %s: %v", com1.Hash, err))
- return
- }
- // Fetch the associated perf result.
- ress1 := &PerfResult{CommitHash: com1.Hash}
- if err := datastore.Get(c, ress1.Key(c), ress1); err != nil {
- logErr(w, r, fmt.Errorf("failed to fetch perf result %s: %v", com1.Hash, err))
- return
- }
-
- // Fetch the old commit.
- var ress0 *PerfResult
- com0 := new(Commit)
- com0.Hash = r.FormValue("commit0")
- if hash, ok := knownTags[com0.Hash]; ok {
- com0.Hash = hash
- }
- if com0.Hash != "" {
- // Have an exact commit hash, fetch directly.
- if err := datastore.Get(c, com0.Key(c), com0); err != nil {
- logErr(w, r, fmt.Errorf("failed to fetch commit %s: %v", com0.Hash, err))
- return
- }
- ress0 = &PerfResult{CommitHash: com0.Hash}
- if err := datastore.Get(c, ress0.Key(c), ress0); err != nil {
- logErr(w, r, fmt.Errorf("failed to fetch perf result for %s: %v", com0.Hash, err))
- return
- }
- } else {
- // Don't have the commit hash, find the previous commit to compare.
- rc := MakePerfResultCache(c, com1, false)
- ress0, err = rc.NextForComparison(com1.Num, "")
- if err != nil {
- logErr(w, r, err)
- return
- }
- if ress0 == nil {
- logErr(w, r, fmt.Errorf("no previous commit with results"))
- return
- }
- // Now that we know the right result, fetch the commit.
- com0.Hash = ress0.CommitHash
- if err := datastore.Get(c, com0.Key(c), com0); err != nil {
- logErr(w, r, fmt.Errorf("failed to fetch commit %s: %v", com0.Hash, err))
- return
- }
- }
-
- res0 := ress0.ParseData()
- res1 := ress1.ParseData()
- var benchmarks []*uiPerfDetailBenchmark
- var list []string
- if kind == "builder" {
- list = pc.BenchmarksForBuilder(builder)
- } else {
- list = pc.BuildersForBenchmark(benchmark)
- }
- for _, other := range list {
- if kind == "builder" {
- benchmark = other
- } else {
- builder = other
- }
- var procs []*uiPerfDetailProcs
- allProcs := pc.ProcList(builder)
- for _, p := range allProcs {
- BenchProcs := fmt.Sprintf("%v-%v", benchmark, p)
- if res0[builder] == nil || res0[builder][BenchProcs] == nil {
- continue
- }
- pp := &uiPerfDetailProcs{Procs: p}
- for metric, val := range res0[builder][BenchProcs].Metrics {
- var pm uiPerfDetailMetric
- pm.Name = metric
- pm.Val0 = fmt.Sprintf("%v", val)
- val1 := uint64(0)
- if res1[builder] != nil && res1[builder][BenchProcs] != nil {
- val1 = res1[builder][BenchProcs].Metrics[metric]
- }
- pm.Val1 = fmt.Sprintf("%v", val1)
- v0 := val
- v1 := val1
- valf := perfDiff(v0, v1)
- pm.Delta = fmt.Sprintf("%+.2f%%", valf)
- pm.Style = perfChangeStyle(pc, valf, builder, BenchProcs, pm.Name)
- pp.Metrics = append(pp.Metrics, pm)
- }
- sort.Sort(pp.Metrics)
- for artifact, hash := range res0[builder][BenchProcs].Artifacts {
- var pm uiPerfDetailMetric
- pm.Val0 = fmt.Sprintf("%v", artifact)
- pm.Link0 = fmt.Sprintf("log/%v", hash)
- pm.Val1 = fmt.Sprintf("%v", artifact)
- if res1[builder] != nil && res1[builder][BenchProcs] != nil && res1[builder][BenchProcs].Artifacts[artifact] != "" {
- pm.Link1 = fmt.Sprintf("log/%v", res1[builder][BenchProcs].Artifacts[artifact])
- }
- pp.Metrics = append(pp.Metrics, pm)
- }
- procs = append(procs, pp)
- }
- benchmarks = append(benchmarks, &uiPerfDetailBenchmark{other, procs})
- }
-
- cfg := new(uiPerfConfig)
- for _, v := range pc.BuildersForBenchmark("") {
- cfg.Builders = append(cfg.Builders, uiPerfConfigElem{v, v == builder})
- }
- for _, v := range pc.BenchmarksForBuilder("") {
- cfg.Benchmarks = append(cfg.Benchmarks, uiPerfConfigElem{v, v == benchmark})
- }
-
- data := &uiPerfDetailTemplateData{d, cfg, kind == "builder", com0, com1, benchmarks}
-
- var buf bytes.Buffer
- if err := uiPerfDetailTemplate.Execute(&buf, data); err != nil {
- logErr(w, r, err)
- return
- }
-
- buf.WriteTo(w)
-}
-
-func perfResultSplit(s string) (builder string, benchmark string, procs int) {
- s1 := strings.Split(s, "|")
- s2 := strings.Split(s1[1], "-")
- procs, _ = strconv.Atoi(s2[1])
- return s1[0], s2[0], procs
-}
-
-type uiPerfDetailTemplateData struct {
- Dashboard *Dashboard
- Config *uiPerfConfig
- KindBuilder bool
- Commit0 *Commit
- Commit1 *Commit
- Benchmarks []*uiPerfDetailBenchmark
-}
-
-type uiPerfDetailBenchmark struct {
- Name string
- Procs []*uiPerfDetailProcs
-}
-
-type uiPerfDetailProcs struct {
- Procs int
- Metrics uiPerfDetailMetrics
-}
-
-type uiPerfDetailMetric struct {
- Name string
- Val0 string
- Val1 string
- Link0 string
- Link1 string
- Delta string
- Style string
-}
-
-type uiPerfDetailMetrics []uiPerfDetailMetric
-
-func (l uiPerfDetailMetrics) Len() int { return len(l) }
-func (l uiPerfDetailMetrics) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
-func (l uiPerfDetailMetrics) Less(i, j int) bool { return l[i].Name < l[j].Name }
-
-var uiPerfDetailTemplate = template.Must(
- template.New("perf_detail.html").Funcs(tmplFuncs).ParseFiles("build/perf_detail.html"),
-)
diff --git a/dashboard/app/build/perf_detail.html b/dashboard/app/build/perf_detail.html
deleted file mode 100644
index 18b3028..0000000
--- a/dashboard/app/build/perf_detail.html
+++ /dev/null
@@ -1,101 +0,0 @@
-<!doctype html>
-<html>
-<head>
- <title>{{$.Dashboard.Name}} Dashboard</title>
- <link rel="stylesheet" href="/static/style.css"/>
- <script type="text/javascript">
- function kindBuilder() {
- document.getElementById('checkBuilder').checked = true;
- document.getElementById('controlBuilder').style.display='inline';
- document.getElementById('controlBenchmark').style.display='none';
- }
- function kindBenchmark() {
- document.getElementById('checkBenchmark').checked = true;
- document.getElementById('controlBenchmark').style.display='inline';
- document.getElementById('controlBuilder').style.display='none';
- }
- window.onload = {{if $.KindBuilder}} kindBuilder {{else}} kindBenchmark {{end}};
- </script>
-</head>
-<body>
- <header id="topbar">
- <h1>Go Dashboard</h1>
- <nav>
- <a href="{{$.Dashboard.RelPath}}">Test</a>
- <a href="{{$.Dashboard.RelPath}}perf">Perf</a>
- <a href="{{$.Dashboard.RelPath}}perfgraph">Graphs</a>
- </nav>
- <div class="clear"></div>
- </header>
-
- <div class="page">
- <div class="diff-container">
- <div class="diff-meta">
- <form>
- <div><b>New: </b><input type="edit" name="commit" value="{{$.Commit1.Hash}}" /> {{shortUser $.Commit1.User}} {{$.Commit1.Time.Format "Mon 02 Jan 15:04"}} {{shortDesc $.Commit1.Desc}} </div>
- <div><b>Old: </b><input type="edit" name="commit0" value="{{$.Commit0.Hash}}" /> {{shortUser $.Commit0.User}} {{$.Commit0.Time.Format "Mon 02 Jan 15:04"}} {{shortDesc $.Commit0.Desc}} </div>
- <div>
- <input id="checkBuilder" type="radio" name="kind" value="builder" required onclick="kindBuilder()">builder</input>
- <input id="checkBenchmark" type="radio" name="kind" value="benchmark" required onclick="kindBenchmark()">benchmark</input>
- <select id="controlBuilder" name="builder">
- {{range $.Config.Builders}}
- <option {{if .Selected}}selected{{end}}>{{.Name}}</option>
- {{end}}
- </select>
- <select id="controlBenchmark" name="benchmark">
- {{range $.Config.Benchmarks}}
- <option {{if .Selected}}selected{{end}}>{{.Name}}</option>
- {{end}}
- </select>
- <input type="submit" value="Refresh" />
- <a href="https://code.google.com/p/go-wiki/wiki/PerfDashboard">Help</a>
- </div>
- </form>
- </div>
- <p></p>
-
- {{range $b := $.Benchmarks}}
- <div class="diff-benchmark">
- <h2>{{$b.Name}}</h2>
- {{range $p := $b.Procs}}
- <div class="diff">
- <h1>GOMAXPROCS={{$p.Procs}}</h1>
- <table>
- <thead>
- <tr>
- <th>Metric</th>
- <th>old</th>
- <th>new</th>
- <th>delta</th>
- </tr>
- </thead>
- <tbody>
- {{range $m := $p.Metrics}}
- <tr>
- <td class="metric">{{$m.Name}}</td>
- {{if $m.Link0}}
- <td><a href="{{$.Dashboard.RelPath}}{{$m.Link0}}">{{$m.Val0}}</td>
- {{else}}
- <td>{{$m.Val0}}</td>
- {{end}}
- {{if $m.Link1}}
- <td><a href="{{$.Dashboard.RelPath}}{{$m.Link1}}">{{$m.Val1}}</td>
- {{else}}
- <td>{{$m.Val1}}</td>
- {{end}}
- <td class="result"><span class="{{$m.Style}}">{{$m.Delta}}</span></td>
- </tr>
- {{end}}
- </tbody>
- </table>
- </div>
- {{end}}
- </div>
- {{end}}
-
- <div class="clear"></div>
- </div>
- <div class="clear"></div>
- </div>
-</body>
-</html>
diff --git a/dashboard/app/build/perf_graph.go b/dashboard/app/build/perf_graph.go
deleted file mode 100644
index 81eb5e1..0000000
--- a/dashboard/app/build/perf_graph.go
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-import (
- "bytes"
- "fmt"
- "html/template"
- "net/http"
- "strconv"
-
- "appengine"
- "appengine/datastore"
-)
-
-func init() {
- for _, d := range dashboards {
- http.HandleFunc(d.RelPath+"perfgraph", perfGraphHandler)
- }
-}
-
-func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
- d := dashboardForRequest(r)
- c := d.Context(appengine.NewContext(r))
- pc, err := GetPerfConfig(c, r)
- if err != nil {
- logErr(w, r, err)
- return
- }
- allBuilders := pc.BuildersForBenchmark("")
- allBenchmarks := pc.BenchmarksForBuilder("")
- allMetrics := pc.MetricsForBenchmark("")
- allProcs := pc.ProcList("")
- r.ParseForm()
- selBuilders := r.Form["builder"]
- selBenchmarks := r.Form["benchmark"]
- selMetrics := r.Form["metric"]
- selProcs := r.Form["procs"]
- if len(selBuilders) == 0 {
- selBuilders = append(selBuilders, allBuilders[0])
- }
- if len(selBenchmarks) == 0 {
- selBenchmarks = append(selBenchmarks, "json")
- }
- if len(selMetrics) == 0 {
- selMetrics = append(selMetrics, "time")
- }
- if len(selProcs) == 0 {
- selProcs = append(selProcs, "1")
- }
- commitFrom := r.FormValue("commit-from")
- if commitFrom == "" {
- commitFrom = lastRelease
- }
- commitTo := r.FormValue("commit-to")
- if commitTo == "" {
- commitTo = "tip"
- }
- // TODO(dvyukov): validate input
-
- // Figure out start and end commit from commitFrom/commitTo.
- startCommitNum := 0
- endCommitNum := 0
- {
- comFrom := &Commit{Hash: knownTags[commitFrom]}
- if err := datastore.Get(c, comFrom.Key(c), comFrom); err != nil {
- logErr(w, r, err)
- return
- }
- startCommitNum = comFrom.Num
-
- retry:
- if commitTo == "tip" {
- p, err := GetPackage(c, "")
- if err != nil {
- logErr(w, r, err)
- return
- }
- endCommitNum = p.NextNum
- } else {
- comTo := &Commit{Hash: knownTags[commitTo]}
- if err := datastore.Get(c, comTo.Key(c), comTo); err != nil {
- logErr(w, r, err)
- return
- }
- endCommitNum = comTo.Num + 1
- }
- if endCommitNum <= startCommitNum {
- // User probably selected from:go1.3 to:go1.2. Fix go1.2 to tip.
- if commitTo == "tip" {
- logErr(w, r, fmt.Errorf("no commits to display (%v-%v)", commitFrom, commitTo))
- return
- }
- commitTo = "tip"
- goto retry
- }
- }
- commitsToDisplay := endCommitNum - startCommitNum
-
- present := func(set []string, s string) bool {
- for _, s1 := range set {
- if s1 == s {
- return true
- }
- }
- return false
- }
-
- cfg := &uiPerfConfig{}
- for _, v := range allBuilders {
- cfg.Builders = append(cfg.Builders, uiPerfConfigElem{v, present(selBuilders, v)})
- }
- for _, v := range allBenchmarks {
- cfg.Benchmarks = append(cfg.Benchmarks, uiPerfConfigElem{v, present(selBenchmarks, v)})
- }
- for _, v := range allMetrics {
- cfg.Metrics = append(cfg.Metrics, uiPerfConfigElem{v, present(selMetrics, v)})
- }
- for _, v := range allProcs {
- cfg.Procs = append(cfg.Procs, uiPerfConfigElem{strconv.Itoa(v), present(selProcs, strconv.Itoa(v))})
- }
- for k := range knownTags {
- cfg.CommitsFrom = append(cfg.CommitsFrom, uiPerfConfigElem{k, commitFrom == k})
- }
- for k := range knownTags {
- cfg.CommitsTo = append(cfg.CommitsTo, uiPerfConfigElem{k, commitTo == k})
- }
- cfg.CommitsTo = append(cfg.CommitsTo, uiPerfConfigElem{"tip", commitTo == "tip"})
-
- var vals [][]float64
- var hints [][]string
- var annotations [][]string
- var certainty [][]bool
- var headers []string
- commits2, err := GetCommits(c, startCommitNum, commitsToDisplay)
- if err != nil {
- logErr(w, r, err)
- return
- }
- for _, builder := range selBuilders {
- for _, metric := range selMetrics {
- for _, benchmark := range selBenchmarks {
- for _, procs := range selProcs {
- benchProcs := fmt.Sprintf("%v-%v", benchmark, procs)
- vv, err := GetPerfMetricsForCommits(c, builder, benchProcs, metric, startCommitNum, commitsToDisplay)
- if err != nil {
- logErr(w, r, err)
- return
- }
- hasdata := false
- for _, v := range vv {
- if v != 0 {
- hasdata = true
- }
- }
- if hasdata {
- noise := pc.NoiseLevel(builder, benchProcs, metric)
- descBuilder := "/" + builder
- descBenchmark := "/" + benchProcs
- descMetric := "/" + metric
- if len(selBuilders) == 1 {
- descBuilder = ""
- }
- if len(selBenchmarks) == 1 && len(selProcs) == 1 {
- descBenchmark = ""
- }
- if len(selMetrics) == 1 && (len(selBuilders) > 1 || len(selBenchmarks) > 1 || len(selProcs) > 1) {
- descMetric = ""
- }
- desc := fmt.Sprintf("%v%v%v", descBuilder, descBenchmark, descMetric)[1:]
- hh := make([]string, commitsToDisplay)
- ann := make([]string, commitsToDisplay)
- valf := make([]float64, commitsToDisplay)
- cert := make([]bool, commitsToDisplay)
- firstval := uint64(0)
- lastval := uint64(0)
- for i, v := range vv {
- cert[i] = true
- if v == 0 {
- if lastval == 0 {
- continue
- }
- cert[i] = false
- v = lastval
- }
- if firstval == 0 {
- firstval = v
- }
- valf[i] = float64(v) / float64(firstval)
- if cert[i] {
- d := ""
- if lastval != 0 {
- diff := perfDiff(lastval, v)
- d = fmt.Sprintf(" (%+.02f%%)", diff)
- if !isNoise(diff, noise) {
- ann[i] = fmt.Sprintf("%+.02f%%", diff)
- }
- }
- hh[i] = fmt.Sprintf("%v%v", v, d)
- } else {
- hh[i] = "NO DATA"
- }
- lastval = v
- }
- vals = append(vals, valf)
- hints = append(hints, hh)
- annotations = append(annotations, ann)
- certainty = append(certainty, cert)
- headers = append(headers, desc)
- }
- }
- }
- }
- }
-
- var commits []perfGraphCommit
- if len(vals) != 0 && len(vals[0]) != 0 {
- idx := 0
- for i := range vals[0] {
- com := commits2[i]
- if com == nil || !com.NeedsBenchmarking {
- continue
- }
- c := perfGraphCommit{Id: idx, Name: fmt.Sprintf("%v (%v)", com.Desc, com.Time.Format("Jan 2, 2006 1:04"))}
- idx++
- for j := range vals {
- c.Vals = append(c.Vals, perfGraphValue{float64(vals[j][i]), certainty[j][i], hints[j][i], annotations[j][i]})
- }
- commits = append(commits, c)
- }
- }
-
- data := &perfGraphData{d, cfg, headers, commits}
-
- var buf bytes.Buffer
- if err := perfGraphTemplate.Execute(&buf, data); err != nil {
- logErr(w, r, err)
- return
- }
-
- buf.WriteTo(w)
-}
-
-var perfGraphTemplate = template.Must(
- template.New("perf_graph.html").ParseFiles("build/perf_graph.html"),
-)
-
-type perfGraphData struct {
- Dashboard *Dashboard
- Config *uiPerfConfig
- Headers []string
- Commits []perfGraphCommit
-}
-
-type perfGraphCommit struct {
- Id int
- Name string
- Vals []perfGraphValue
-}
-
-type perfGraphValue struct {
- Val float64
- Certainty bool
- Hint string
- Ann string
-}
diff --git a/dashboard/app/build/perf_graph.html b/dashboard/app/build/perf_graph.html
deleted file mode 100644
index da1c0d0..0000000
--- a/dashboard/app/build/perf_graph.html
+++ /dev/null
@@ -1,120 +0,0 @@
-<!doctype html>
-<html>
- <head>
- <title>{{$.Dashboard.Name}} Dashboard</title>
- <link rel="stylesheet" href="/static/style.css"/>
- <style>
- .graph-container { background: #eee; }
- </style>
-
- <script type="text/javascript" src="https://www.google.com/jsapi"></script>
- <script type="text/javascript">
- google.load("visualization", "1", {packages:["corechart"]});
- google.setOnLoadCallback(drawCharts);
- function drawCharts() {
- var data = new google.visualization.DataTable();
- data.addColumn({type: 'number', label: 'Commit'});
- data.addColumn({type: 'number'});
- data.addColumn({type: 'string', role: 'tooltip'});
- {{range $.Headers}}
- data.addColumn({type: 'number', label: '{{.}}'});
- data.addColumn({type: 'boolean', role: 'certainty'});
- data.addColumn({type: 'string', role: 'tooltip'});
- data.addColumn({type: 'string', role: 'annotation'});
- {{end}}
- data.addRows([
- {{range $.Commits}}
- [ {{.Id}}, 1, "{{.Name}}",
- {{range .Vals}}
- {{if .Val}}
- {{.Val}}, {{.Certainty}}, '{{.Hint}}', '{{.Ann}}',
- {{else}}
- ,,,,
- {{end}}
- {{end}}
- ],
- {{end}}
- ]);
- new google.visualization.LineChart(document.getElementById('graph_div')).
- draw(data, {
- width: "100%",
- height: 700,
- legend: {position: "bottom"},
- focusTarget: "category",
- hAxis: {textPosition: "none"},
- chartArea: {left: "10%", top: "5%", width: "85%", height:"80%"},
- explorer: {axis: 'horizontal', maxZoomIn: 0, maxZoomOut: 1, zoomDelta: 1.2, keepInBounds: true}
- })
- }
- </script>
-</head>
-<body>
-
- <header id="topbar">
- <h1>Go Dashboard</h1>
- <nav>
- <a href="{{$.Dashboard.RelPath}}">Test</a>
- <a href="{{$.Dashboard.RelPath}}perf">Perf</a>
- <a href="{{$.Dashboard.RelPath}}perfgraph">Graphs</a>
- </nav>
- <div class="clear"></div>
- </header>
-
- <div class="page">
- <div id="graph_div" class="main-content graph-container">
- </div>
-
- <aside>
- <form>
- <div class="panel">
- <h1>Builders</h1>
- {{range $.Config.Builders}}
- <input type="checkbox" name="builder" value="{{.Name}}" {{if .Selected}}checked{{end}}>{{.Name}}</input><br>
- {{end}}
- </div>
-
- <div class="panel">
- <h1>Benchmarks</h1>
- {{range $.Config.Benchmarks}}
- <input type="checkbox" name="benchmark" value="{{.Name}}" {{if .Selected}}checked{{end}}>{{.Name}}</input><br>
- {{end}}
- </div>
-
- <div class="panel">
- <h1>Procs</h1>
- {{range $.Config.Procs}}
- <input type="checkbox" name="procs" value="{{.Name}}" {{if .Selected}}checked{{end}}>{{.Name}}</input><br>
- {{end}}
- </div>
-
- <div class="panel">
- <h1>Metrics</h1>
- {{range $.Config.Metrics}}
- <input type="checkbox" name="metric" value="{{.Name}}" {{if .Selected}}checked{{end}}>{{.Name}}</input><br>
- {{end}}
- </div>
-
- <div class="panel">
- <h1>Commits</h1>
- <b>From:</b>
- <select required name="commit-from">
- {{range $.Config.CommitsFrom}}
- <option {{if .Selected}}selected{{end}}>{{.Name}}</option>
- {{end}}
- </select>
- <b>To:</b>
- <select required name="commit-to">
- {{range $.Config.CommitsTo}}
- <option {{if .Selected}}selected{{end}}>{{.Name}}</option>
- {{end}}
- </select>
- </div>
-
- <input class="button" type="submit" value="Refresh" name="refresh"/>
- <a href="https://code.google.com/p/go-wiki/wiki/PerfDashboard">Help</a>
- </form>
- </aside>
- <div class="clear"></div>
- </div>
-</body>
-</html>
diff --git a/dashboard/app/build/perf_learn.go b/dashboard/app/build/perf_learn.go
deleted file mode 100644
index 683ba60..0000000
--- a/dashboard/app/build/perf_learn.go
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-import (
- "bytes"
- "fmt"
- "html/template"
- "net/http"
- "sort"
-
- "appengine"
- "appengine/datastore"
-)
-
-func init() {
- http.HandleFunc("/perflearn", perfLearnHandler)
-}
-
-const (
- learnPercentile = 0.95
- learnSignalMultiplier = 1.1
- learnMinSignal = 0.5
-)
-
-func perfLearnHandler(w http.ResponseWriter, r *http.Request) {
- d := dashboardForRequest(r)
- c := d.Context(appengine.NewContext(r))
-
- pc, err := GetPerfConfig(c, r)
- if err != nil {
- logErr(w, r, err)
- return
- }
-
- p, err := GetPackage(c, "")
- if err != nil {
- logErr(w, r, err)
- return
- }
-
- update := r.FormValue("update") != ""
- noise := make(map[string]string)
-
- data := &perfLearnData{}
-
- commits, err := GetCommits(c, 0, p.NextNum)
- if err != nil {
- logErr(w, r, err)
- return
- }
-
- for _, builder := range pc.BuildersForBenchmark("") {
- for _, benchmark := range pc.BenchmarksForBuilder(builder) {
- for _, metric := range pc.MetricsForBenchmark(benchmark) {
- for _, procs := range pc.ProcList(builder) {
- values, err := GetPerfMetricsForCommits(c, builder, fmt.Sprintf("%v-%v", benchmark, procs), metric, 0, p.NextNum)
- if err != nil {
- logErr(w, r, err)
- return
- }
- var dd []float64
- last := uint64(0)
- for i, v := range values {
- if v == 0 {
- if com := commits[i]; com == nil || com.NeedsBenchmarking {
- last = 0
- }
- continue
- }
- if last != 0 {
- v1 := v
- if v1 < last {
- v1, last = last, v1
- }
- diff := float64(v1)/float64(last)*100 - 100
- dd = append(dd, diff)
- }
- last = v
- }
- if len(dd) == 0 {
- continue
- }
- sort.Float64s(dd)
-
- baseIdx := int(float64(len(dd)) * learnPercentile)
- baseVal := dd[baseIdx]
- signalVal := baseVal * learnSignalMultiplier
- if signalVal < learnMinSignal {
- signalVal = learnMinSignal
- }
- signalIdx := -1
- noiseNum := 0
- signalNum := 0
-
- var diffs []*perfLearnDiff
- for i, d := range dd {
- if d > 3*signalVal {
- d = 3 * signalVal
- }
- diffs = append(diffs, &perfLearnDiff{Num: i, Val: d})
- if signalIdx == -1 && d >= signalVal {
- signalIdx = i
- }
- if d < signalVal {
- noiseNum++
- } else {
- signalNum++
- }
- }
- diffs[baseIdx].Hint = "95%"
- if signalIdx != -1 {
- diffs[signalIdx].Hint = "signal"
- }
- diffs = diffs[len(diffs)*4/5:]
- name := fmt.Sprintf("%v/%v-%v/%v", builder, benchmark, procs, metric)
- data.Entries = append(data.Entries, &perfLearnEntry{len(data.Entries), name, baseVal, noiseNum, signalVal, signalNum, diffs})
-
- if len(dd) >= 100 || r.FormValue("force") != "" {
- nname := fmt.Sprintf("%v|%v-%v", builder, benchmark, procs)
- n := noise[nname] + fmt.Sprintf("|%v=%.2f", metric, signalVal)
- noise[nname] = n
- }
- }
- }
- }
- }
-
- if update {
- var noiseLevels []string
- for k, v := range noise {
- noiseLevels = append(noiseLevels, k+v)
- }
- tx := func(c appengine.Context) error {
- pc, err := GetPerfConfig(c, r)
- if err != nil {
- return err
- }
- pc.NoiseLevels = noiseLevels
- if _, err := datastore.Put(c, PerfConfigKey(c), pc); err != nil {
- return fmt.Errorf("putting PerfConfig: %v", err)
- }
- return nil
- }
- if err := datastore.RunInTransaction(c, tx, nil); err != nil {
- logErr(w, r, err)
- return
- }
- }
-
- var buf bytes.Buffer
- if err := perfLearnTemplate.Execute(&buf, data); err != nil {
- logErr(w, r, err)
- return
- }
-
- buf.WriteTo(w)
-}
-
-var perfLearnTemplate = template.Must(
- template.New("perf_learn.html").Funcs(tmplFuncs).ParseFiles("build/perf_learn.html"),
-)
-
-type perfLearnData struct {
- Entries []*perfLearnEntry
-}
-
-type perfLearnEntry struct {
- Num int
- Name string
- BaseVal float64
- NoiseNum int
- SignalVal float64
- SignalNum int
- Diffs []*perfLearnDiff
-}
-
-type perfLearnDiff struct {
- Num int
- Val float64
- Hint string
-}
diff --git a/dashboard/app/build/perf_learn.html b/dashboard/app/build/perf_learn.html
deleted file mode 100644
index 294e957..0000000
--- a/dashboard/app/build/perf_learn.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<!doctype html>
-<html>
- <head>
- <script type="text/javascript" src="https://www.google.com/jsapi"></script>
- <script type="text/javascript">
- google.load("visualization", "1", {packages:["corechart"]});
- google.setOnLoadCallback(drawCharts);
- function drawCharts() {
- {
- {{range $ent := $.Entries}}
- var data = new google.visualization.DataTable();
- data.addColumn('number', 'idx');
- data.addColumn('number', '95%');
- data.addColumn({type: 'boolean', role: 'certainty'});
- data.addColumn('number', 'signal');
- data.addColumn({type: 'boolean', role: 'certainty'});
- data.addColumn('number', 'diff');
- data.addColumn({type: 'string', role: 'annotation'});
- data.addRows([
- {{range .Diffs}} [{{.Num}}, {{$ent.BaseVal}}, false, {{$ent.SignalVal}}, false, {{.Val}}, '{{.Hint}}'], {{end}}
- ]);
- new google.visualization.LineChart(document.getElementById('graph{{.Num}}')).
- draw(data, {
- width: 600,
- height: 200,
- legend: {position: "none"},
- vAxis: {minValue: 0},
- chartArea: {left: "10%", top: "1%", width: "90%", height:"95%"}
- }
- )
- {{end}}
- }
- }
- </script>
- </head>
-
- <body>
- {{range $.Entries}}
- <p>
- {{.Name}}: base={{printf "%.2f[%d]" .BaseVal .NoiseNum}} signal={{printf "%.2f[%d]" .SignalVal .SignalNum}}
- <div id="graph{{.Num}}" width="100px" height="100px"> </div>
- </p>
- {{end}}
- </body>
-</html>
diff --git a/dashboard/app/build/perf_notify.txt b/dashboard/app/build/perf_notify.txt
deleted file mode 100644
index c5e8ebe..0000000
--- a/dashboard/app/build/perf_notify.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-{{if .Commit}}Change {{shortHash .Commit.Hash}} caused perf changes on {{.Builder}}:
-
-{{.Commit.Desc}}
-
-http://code.google.com/p/go/source/detail?r={{shortHash .Commit.Hash}}
-{{else}}This changed caused perf changes on {{.Builder}}:
-{{end}}
-{{range $b := .Benchmarks}}
-{{printf "%-16s %12s %12s %10s" $b.Name "old" "new" "delta"}}
-{{range $m := $b.Metrics}}{{printf "%-16s %12v %12v %+10.2f" $m.Name $m.Old $m.New $m.Delta}}
-{{end}}{{end}}
-{{.Url}}
-
diff --git a/dashboard/app/build/test.go b/dashboard/app/build/test.go
deleted file mode 100644
index 34a1c39..0000000
--- a/dashboard/app/build/test.go
+++ /dev/null
@@ -1,378 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-// TODO(adg): test authentication
-// TODO(adg): refactor to use appengine/aetest instead
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "net/http/httptest"
- "net/url"
- "strings"
- "time"
-
- "appengine"
- "appengine/datastore"
-)
-
-func init() {
- http.HandleFunc("/buildtest", testHandler)
-}
-
-var testEntityKinds = []string{
- "Package",
- "Commit",
- "CommitRun",
- "Result",
- "PerfResult",
- "PerfMetricRun",
- "PerfConfig",
- "PerfTodo",
- "Log",
-}
-
-const testPkg = "golang.org/x/test"
-
-var testPackage = &Package{Name: "Test", Kind: "subrepo", Path: testPkg}
-
-var testPackages = []*Package{
- {Name: "Go", Path: ""},
- testPackage,
-}
-
-var tCommitTime = time.Now().Add(-time.Hour * 24 * 7)
-
-func tCommit(hash, parentHash, path string, bench bool) *Commit {
- tCommitTime.Add(time.Hour) // each commit should have a different time
- return &Commit{
- PackagePath: path,
- Hash: hash,
- ParentHash: parentHash,
- Time: tCommitTime,
- User: "adg",
- Desc: "change description " + hash,
- NeedsBenchmarking: bench,
- }
-}
-
-var testRequests = []struct {
- path string
- vals url.Values
- req interface{}
- res interface{}
-}{
- // Packages
- {"/packages", url.Values{"kind": {"subrepo"}}, nil, []*Package{testPackage}},
-
- // Go repo
- {"/commit", nil, tCommit("0001", "0000", "", true), nil},
- {"/commit", nil, tCommit("0002", "0001", "", false), nil},
- {"/commit", nil, tCommit("0003", "0002", "", true), nil},
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
- {"/result", nil, &Result{Builder: "linux-386", Hash: "0001", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
- {"/result", nil, &Result{Builder: "linux-386", Hash: "0002", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
-
- // Other builders, to test the UI.
- {"/result", nil, &Result{Builder: "linux-amd64", Hash: "0001", OK: true}, nil},
- {"/result", nil, &Result{Builder: "linux-amd64-race", Hash: "0001", OK: true}, nil},
- {"/result", nil, &Result{Builder: "netbsd-386", Hash: "0001", OK: true}, nil},
- {"/result", nil, &Result{Builder: "plan9-386", Hash: "0001", OK: true}, nil},
- {"/result", nil, &Result{Builder: "windows-386", Hash: "0001", OK: true}, nil},
- {"/result", nil, &Result{Builder: "windows-amd64", Hash: "0001", OK: true}, nil},
- {"/result", nil, &Result{Builder: "windows-amd64-race", Hash: "0001", OK: true}, nil},
- {"/result", nil, &Result{Builder: "linux-amd64-temp", Hash: "0001", OK: true}, nil},
-
- // multiple builders
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
- {"/result", nil, &Result{Builder: "linux-amd64", Hash: "0003", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0002"}}},
-
- // branches
- {"/commit", nil, tCommit("0004", "0003", "", false), nil},
- {"/commit", nil, tCommit("0005", "0002", "", false), nil},
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0005"}}},
- {"/result", nil, &Result{Builder: "linux-386", Hash: "0005", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0004"}}},
- {"/result", nil, &Result{Builder: "linux-386", Hash: "0004", OK: false}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
-
- // logs
- {"/result", nil, &Result{Builder: "linux-386", Hash: "0003", OK: false, Log: "test"}, nil},
- {"/log/a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", nil, nil, "test"},
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, nil},
-
- // repeat failure (shouldn't re-send mail)
- {"/result", nil, &Result{Builder: "linux-386", Hash: "0003", OK: false, Log: "test"}, nil},
-
- // non-Go repos
- {"/commit", nil, tCommit("1001", "0000", testPkg, false), nil},
- {"/commit", nil, tCommit("1002", "1001", testPkg, false), nil},
- {"/commit", nil, tCommit("1003", "1002", testPkg, false), nil},
- {"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, &Todo{Kind: "build-package", Data: &Commit{Hash: "1003"}}},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1003", GoHash: "0001", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, &Todo{Kind: "build-package", Data: &Commit{Hash: "1002"}}},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1002", GoHash: "0001", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, &Todo{Kind: "build-package", Data: &Commit{Hash: "1001"}}},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1001", GoHash: "0001", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, nil},
- {"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0002"}}, nil, &Todo{Kind: "build-package", Data: &Commit{Hash: "1003"}}},
-
- // re-build Go revision for stale subrepos
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0005"}}},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1001", GoHash: "0005", OK: false, Log: "boo"}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, nil},
-
- // benchmarks
- // build-go-commit must have precedence over benchmark-go-commit
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0005"}}},
- // drain build-go-commit todo
- {"/result", nil, &Result{Builder: "linux-amd64", Hash: "0005", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0004"}}},
- {"/result", nil, &Result{Builder: "linux-amd64", Hash: "0004", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0002"}}},
- {"/result", nil, &Result{Builder: "linux-amd64", Hash: "0002", OK: true}, nil},
- // drain sub-repo todos
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1001", GoHash: "0005", OK: false}, nil},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1002", GoHash: "0005", OK: false}, nil},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1003", GoHash: "0005", OK: false}, nil},
- // now we must get benchmark todo
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0003", PerfResults: []string{}}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "http", Hash: "0003", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0003", PerfResults: []string{"http"}}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "json", Hash: "0003", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0003", PerfResults: []string{"http", "json"}}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0003", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0001", PerfResults: []string{}}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "http", Hash: "0001", OK: true}, nil},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0001", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, nil},
- // create new commit, it must appear in todo
- {"/commit", nil, tCommit("0006", "0005", "", true), nil},
- // drain build-go-commit todo
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0006"}}},
- {"/result", nil, &Result{Builder: "linux-amd64", Hash: "0006", OK: true}, nil},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1003", GoHash: "0006", OK: false}, nil},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1002", GoHash: "0006", OK: false}, nil},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1001", GoHash: "0006", OK: false}, nil},
- // now we must get benchmark todo
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0006", PerfResults: []string{}}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "http", Hash: "0006", OK: true}, nil},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0006", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, nil},
- // create new benchmark, all commits must re-appear in todo
- {"/commit", nil, tCommit("0007", "0006", "", true), nil},
- // drain build-go-commit todo
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0007"}}},
- {"/result", nil, &Result{Builder: "linux-amd64", Hash: "0007", OK: true}, nil},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1003", GoHash: "0007", OK: false}, nil},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1002", GoHash: "0007", OK: false}, nil},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1001", GoHash: "0007", OK: false}, nil},
- // now we must get benchmark todo
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0007", PerfResults: []string{}}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "bson", Hash: "0007", OK: true}, nil},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0007", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0007", PerfResults: []string{"bson"}}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0007", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0006", PerfResults: []string{"http"}}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0006", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0001", PerfResults: []string{"http"}}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0001", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0003", PerfResults: []string{"http", "json"}}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0003", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, nil},
- // attach second builder
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0007"}}},
- // drain build-go-commit todo
- {"/result", nil, &Result{Builder: "linux-386", Hash: "0007", OK: true}, nil},
- {"/result", nil, &Result{Builder: "linux-386", Hash: "0006", OK: true}, nil},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1003", GoHash: "0007", OK: false}, nil},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1002", GoHash: "0007", OK: false}, nil},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1001", GoHash: "0007", OK: false}, nil},
- // now we must get benchmark todo
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0007"}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-386", Benchmark: "meta-done", Hash: "0007", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0006"}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-386", Benchmark: "meta-done", Hash: "0006", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0001"}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-386", Benchmark: "meta-done", Hash: "0001", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0003"}}},
- {"/perf-result", nil, &PerfRequest{Builder: "linux-386", Benchmark: "meta-done", Hash: "0003", OK: true}, nil},
- {"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, nil},
-}
-
-func testHandler(w http.ResponseWriter, r *http.Request) {
- if !appengine.IsDevAppServer() {
- fmt.Fprint(w, "These tests must be run under the dev_appserver.")
- return
- }
- c := appengine.NewContext(r)
- if err := nukeEntities(c, testEntityKinds); err != nil {
- logErr(w, r, err)
- return
- }
- if r.FormValue("nukeonly") != "" {
- fmt.Fprint(w, "OK")
- return
- }
-
- for _, p := range testPackages {
- if _, err := datastore.Put(c, p.Key(c), p); err != nil {
- logErr(w, r, err)
- return
- }
- }
-
- origReq := *r
- defer func() {
- // HACK: We need to clobber the original request (see below)
- // so make sure we fix it before exiting the handler.
- *r = origReq
- }()
- for i, t := range testRequests {
- c.Infof("running test %d %s vals='%q' req='%q' res='%q'", i, t.path, t.vals, t.req, t.res)
- errorf := func(format string, args ...interface{}) {
- fmt.Fprintf(w, "%d %s: ", i, t.path)
- fmt.Fprintf(w, format, args...)
- fmt.Fprintln(w)
- }
- var body io.ReadWriter
- if t.req != nil {
- body = new(bytes.Buffer)
- json.NewEncoder(body).Encode(t.req)
- }
- url := "http://" + domain + t.path
- if t.vals != nil {
- url += "?" + t.vals.Encode() + "&version=2"
- } else {
- url += "?version=2"
- }
- req, err := http.NewRequest("POST", url, body)
- if err != nil {
- logErr(w, r, err)
- return
- }
- if t.req != nil {
- req.Method = "POST"
- }
- req.Header = origReq.Header
- rec := httptest.NewRecorder()
-
- // Make the request
- *r = *req // HACK: App Engine uses the request pointer
- // as a map key to resolve Contexts.
- http.DefaultServeMux.ServeHTTP(rec, r)
-
- if rec.Code != 0 && rec.Code != 200 {
- errorf(rec.Body.String())
- return
- }
- c.Infof("response='%v'", rec.Body.String())
- resp := new(dashResponse)
-
- // If we're expecting a *Todo value,
- // prime the Response field with a Todo and a Commit inside it.
- if t.path == "/todo" {
- resp.Response = &Todo{Data: &Commit{}}
- }
-
- if strings.HasPrefix(t.path, "/log/") {
- resp.Response = rec.Body.String()
- } else {
- err := json.NewDecoder(rec.Body).Decode(resp)
- if err != nil {
- errorf("decoding response: %v", err)
- return
- }
- }
- if e, ok := t.res.(string); ok {
- g, ok := resp.Response.(string)
- if !ok {
- errorf("Response not string: %T", resp.Response)
- return
- }
- if g != e {
- errorf("response mismatch: got %q want %q", g, e)
- return
- }
- }
- if e, ok := t.res.(*Todo); ok {
- g, ok := resp.Response.(*Todo)
- if !ok {
- errorf("Response not *Todo: %T", resp.Response)
- return
- }
- if e.Data == nil && g.Data != nil {
- errorf("Response.Data should be nil, got: %v", g.Data)
- return
- }
- if g.Data == nil {
- errorf("Response.Data is nil, want: %v", e.Data)
- return
- }
- gd, ok := g.Data.(*Commit)
- if !ok {
- errorf("Response.Data not *Commit: %T", g.Data)
- return
- }
- if g.Kind != e.Kind {
- errorf("kind don't match: got %q, want %q", g.Kind, e.Kind)
- return
- }
- ed := e.Data.(*Commit)
- if ed.Hash != gd.Hash {
- errorf("hashes don't match: got %q, want %q", gd.Hash, ed.Hash)
- return
- }
- if len(gd.PerfResults) != len(ed.PerfResults) {
- errorf("result data len don't match: got %v, want %v", len(gd.PerfResults), len(ed.PerfResults))
- return
- }
- for i := range gd.PerfResults {
- if gd.PerfResults[i] != ed.PerfResults[i] {
- errorf("result data %v don't match: got %v, want %v", i, gd.PerfResults[i], ed.PerfResults[i])
- return
- }
- }
- }
- if t.res == nil && resp.Response != nil {
- errorf("response mismatch: got %q expected <nil>", resp.Response)
- return
- }
- }
- fmt.Fprint(w, "PASS\nYou should see only one mail notification (for 0003/linux-386) in the dev_appserver logs.")
-}
-
-func nukeEntities(c appengine.Context, kinds []string) error {
- if !appengine.IsDevAppServer() {
- return errors.New("can't nuke production data")
- }
- var keys []*datastore.Key
- for _, kind := range kinds {
- q := datastore.NewQuery(kind).KeysOnly()
- for t := q.Run(c); ; {
- k, err := t.Next(nil)
- if err == datastore.Done {
- break
- }
- if err != nil {
- return err
- }
- keys = append(keys, k)
- }
- }
- return datastore.DeleteMulti(c, keys)
-}
diff --git a/dashboard/app/build/ui.go b/dashboard/app/build/ui.go
deleted file mode 100644
index c2cf7c5..0000000
--- a/dashboard/app/build/ui.go
+++ /dev/null
@@ -1,460 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// TODO(adg): packages at weekly/release
-// TODO(adg): some means to register new packages
-
-// +build appengine
-
-package build
-
-import (
- "bytes"
- "errors"
- "fmt"
- "html/template"
- "net/http"
- "regexp"
- "sort"
- "strconv"
- "strings"
-
- "cache"
-
- "appengine"
- "appengine/datastore"
-)
-
-func init() {
- for _, d := range dashboards {
- http.HandleFunc(d.RelPath, uiHandler)
- }
-}
-
-// uiHandler draws the build status page.
-func uiHandler(w http.ResponseWriter, r *http.Request) {
- d := dashboardForRequest(r)
- c := d.Context(appengine.NewContext(r))
- now := cache.Now(c)
- key := "build-ui"
-
- page, _ := strconv.Atoi(r.FormValue("page"))
- if page < 0 {
- page = 0
- }
- key += fmt.Sprintf("-page%v", page)
-
- branch := r.FormValue("branch")
- if branch != "" {
- key += "-branch-" + branch
- }
-
- repo := r.FormValue("repo")
- if repo != "" {
- key += "-repo-" + repo
- }
-
- var b []byte
- if cache.Get(r, now, key, &b) {
- w.Write(b)
- return
- }
-
- pkg := &Package{} // empty package is the main repository
- if repo != "" {
- var err error
- pkg, err = GetPackage(c, repo)
- if err != nil {
- logErr(w, r, err)
- return
- }
- }
- commits, err := dashCommits(c, pkg, page, branch)
- if err != nil {
- logErr(w, r, err)
- return
- }
- builders := commitBuilders(commits)
-
- var tipState *TagState
- if pkg.Kind == "" && page == 0 && (branch == "" || branch == "default") {
- // only show sub-repo state on first page of normal repo view
- tipState, err = TagStateByName(c, "tip")
- if err != nil {
- logErr(w, r, err)
- return
- }
- }
-
- p := &Pagination{}
- if len(commits) == commitsPerPage {
- p.Next = page + 1
- }
- if page > 0 {
- p.Prev = page - 1
- p.HasPrev = true
- }
- data := &uiTemplateData{d, pkg, commits, builders, tipState, p, branch}
-
- var buf bytes.Buffer
- if err := uiTemplate.Execute(&buf, data); err != nil {
- logErr(w, r, err)
- return
- }
-
- cache.Set(r, now, key, buf.Bytes())
-
- buf.WriteTo(w)
-}
-
-type Pagination struct {
- Next, Prev int
- HasPrev bool
-}
-
-// dashCommits gets a slice of the latest Commits to the current dashboard.
-// If page > 0 it paginates by commitsPerPage.
-func dashCommits(c appengine.Context, pkg *Package, page int, branch string) ([]*Commit, error) {
- offset := page * commitsPerPage
- q := datastore.NewQuery("Commit").
- Ancestor(pkg.Key(c)).
- Order("-Num")
-
- var commits []*Commit
- if branch == "" {
- _, err := q.Limit(commitsPerPage).Offset(offset).
- GetAll(c, &commits)
- return commits, err
- }
-
- // Look for commits on a specific branch.
- for t, n := q.Run(c), 0; len(commits) < commitsPerPage && n < 1000; {
- var c Commit
- _, err := t.Next(&c)
- if err == datastore.Done {
- break
- }
- if err != nil {
- return nil, err
- }
- if !isBranchCommit(&c, branch) {
- continue
- }
- if n >= offset {
- commits = append(commits, &c)
- }
- n++
- }
- return commits, nil
-}
-
-// isBranchCommit reports whether the given commit is on the specified branch.
-// It does so by examining the commit description, so there will be some bad
-// matches where the branch commits do not begin with the "[branch]" prefix.
-func isBranchCommit(c *Commit, b string) bool {
- d := strings.TrimSpace(c.Desc)
- if b == "default" {
- return !strings.HasPrefix(d, "[")
- }
- return strings.HasPrefix(d, "["+b+"]")
-}
-
-// commitBuilders returns the names of the builders that provided
-// Results for the provided commits.
-func commitBuilders(commits []*Commit) []string {
- builders := make(map[string]bool)
- for _, commit := range commits {
- for _, r := range commit.Results() {
- builders[r.Builder] = true
- }
- }
- k := keys(builders)
- sort.Sort(builderOrder(k))
- return k
-}
-
-func keys(m map[string]bool) (s []string) {
- for k := range m {
- s = append(s, k)
- }
- sort.Strings(s)
- return
-}
-
-// builderOrder implements sort.Interface, sorting builder names
-// ("darwin-amd64", etc) first by builderPriority and then alphabetically.
-type builderOrder []string
-
-func (s builderOrder) Len() int { return len(s) }
-func (s builderOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-func (s builderOrder) Less(i, j int) bool {
- pi, pj := builderPriority(s[i]), builderPriority(s[j])
- if pi == pj {
- return s[i] < s[j]
- }
- return pi < pj
-}
-
-func builderPriority(builder string) (p int) {
- // Put -temp builders at the end, always.
- if strings.HasSuffix(builder, "-temp") {
- defer func() { p += 20 }()
- }
- // Group race builders together.
- if isRace(builder) {
- return 1
- }
- // If the OS has a specified priority, use it.
- if p, ok := osPriority[builderOS(builder)]; ok {
- return p
- }
- // The rest.
- return 10
-}
-
-func isRace(s string) bool {
- return strings.Contains(s, "-race-") || strings.HasSuffix(s, "-race")
-}
-
-func unsupported(builder string) bool {
- if strings.HasSuffix(builder, "-temp") {
- return true
- }
- return unsupportedOS(builderOS(builder))
-}
-
-func unsupportedOS(os string) bool {
- if os == "race" {
- return false
- }
- p, ok := osPriority[os]
- return !ok || p > 0
-}
-
-// Priorities for specific operating systems.
-var osPriority = map[string]int{
- "darwin": 0,
- "freebsd": 0,
- "linux": 0,
- "windows": 0,
- // race == 1
- "openbsd": 2,
- "netbsd": 3,
- "dragonfly": 4,
-}
-
-// TagState represents the state of all Packages at a Tag.
-type TagState struct {
- Tag *Commit
- Packages []*PackageState
-}
-
-// PackageState represents the state of a Package at a Tag.
-type PackageState struct {
- Package *Package
- Commit *Commit
-}
-
-// TagStateByName fetches the results for all Go subrepos at the specified Tag.
-func TagStateByName(c appengine.Context, name string) (*TagState, error) {
- tag, err := GetTag(c, name)
- if err != nil {
- return nil, err
- }
- pkgs, err := Packages(c, "subrepo")
- if err != nil {
- return nil, err
- }
- var st TagState
- for _, pkg := range pkgs {
- com, err := pkg.LastCommit(c)
- if err != nil {
- c.Warningf("%v: no Commit found: %v", pkg, err)
- continue
- }
- st.Packages = append(st.Packages, &PackageState{pkg, com})
- }
- st.Tag, err = tag.Commit(c)
- if err != nil {
- return nil, err
- }
- return &st, nil
-}
-
-type uiTemplateData struct {
- Dashboard *Dashboard
- Package *Package
- Commits []*Commit
- Builders []string
- TipState *TagState
- Pagination *Pagination
- Branch string
-}
-
-var uiTemplate = template.Must(
- template.New("ui.html").Funcs(tmplFuncs).ParseFiles("build/ui.html"),
-)
-
-var tmplFuncs = template.FuncMap{
- "buildDashboards": buildDashboards,
- "builderOS": builderOS,
- "builderSpans": builderSpans,
- "builderSubheading": builderSubheading,
- "builderTitle": builderTitle,
- "repoURL": repoURL,
- "shortDesc": shortDesc,
- "shortHash": shortHash,
- "shortUser": shortUser,
- "tail": tail,
- "unsupported": unsupported,
-}
-
-func splitDash(s string) (string, string) {
- i := strings.Index(s, "-")
- if i >= 0 {
- return s[:i], s[i+1:]
- }
- return s, ""
-}
-
-// builderOS returns the os tag for a builder string
-func builderOS(s string) string {
- os, _ := splitDash(s)
- return os
-}
-
-// builderOSOrRace returns the builder OS or, if it is a race builder, "race".
-func builderOSOrRace(s string) string {
- if isRace(s) {
- return "race"
- }
- return builderOS(s)
-}
-
-// builderArch returns the arch tag for a builder string
-func builderArch(s string) string {
- _, arch := splitDash(s)
- arch, _ = splitDash(arch) // chop third part
- return arch
-}
-
-// builderSubheading returns a short arch tag for a builder string
-// or, if it is a race builder, the builder OS.
-func builderSubheading(s string) string {
- if isRace(s) {
- return builderOS(s)
- }
- arch := builderArch(s)
- switch arch {
- case "amd64":
- return "x64"
- }
- return arch
-}
-
-// builderArchChar returns the architecture letter for a builder string
-func builderArchChar(s string) string {
- arch := builderArch(s)
- switch arch {
- case "386":
- return "8"
- case "amd64":
- return "6"
- case "arm":
- return "5"
- }
- return arch
-}
-
-type builderSpan struct {
- N int
- OS string
- Unsupported bool
-}
-
-// builderSpans creates a list of tags showing
-// the builder's operating system names, spanning
-// the appropriate number of columns.
-func builderSpans(s []string) []builderSpan {
- var sp []builderSpan
- for len(s) > 0 {
- i := 1
- os := builderOSOrRace(s[0])
- u := unsupportedOS(os) || strings.HasSuffix(s[0], "-temp")
- for i < len(s) && builderOSOrRace(s[i]) == os {
- i++
- }
- sp = append(sp, builderSpan{i, os, u})
- s = s[i:]
- }
- return sp
-}
-
-// builderTitle formats "linux-amd64-foo" as "linux amd64 foo".
-func builderTitle(s string) string {
- return strings.Replace(s, "-", " ", -1)
-}
-
-// buildDashboards returns the known public dashboards.
-func buildDashboards() []*Dashboard {
- return dashboards
-}
-
-// shortDesc returns the first line of a description.
-func shortDesc(desc string) string {
- if i := strings.Index(desc, "\n"); i != -1 {
- desc = desc[:i]
- }
- return limitStringLength(desc, 100)
-}
-
-// shortHash returns a short version of a hash.
-func shortHash(hash string) string {
- if len(hash) > 12 {
- hash = hash[:12]
- }
- return hash
-}
-
-// shortUser returns a shortened version of a user string.
-func shortUser(user string) string {
- if i, j := strings.Index(user, "<"), strings.Index(user, ">"); 0 <= i && i < j {
- user = user[i+1 : j]
- }
- if i := strings.Index(user, "@"); i >= 0 {
- return user[:i]
- }
- return user
-}
-
-// repoRe matches Google Code repositories and subrepositories (without paths).
-var repoRe = regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\-]+)(\.[a-z0-9\-]+)?$`)
-
-// repoURL returns the URL of a change at a Google Code repository or subrepo.
-func repoURL(dashboard, hash, packagePath string) (string, error) {
- if packagePath == "" {
- if dashboard == "Gccgo" {
- return "https://code.google.com/p/gofrontend/source/detail?r=" + hash, nil
- }
- return "https://code.google.com/p/go/source/detail?r=" + hash, nil
- }
- m := repoRe.FindStringSubmatch(packagePath)
- if m == nil {
- return "", errors.New("unrecognized package: " + packagePath)
- }
- url := "https://code.google.com/p/" + m[1] + "/source/detail?r=" + hash
- if len(m) > 2 {
- url += "&repo=" + m[2][1:]
- }
- return url, nil
-}
-
-// tail returns the trailing n lines of s.
-func tail(n int, s string) string {
- lines := strings.Split(s, "\n")
- if len(lines) < n {
- return s
- }
- return strings.Join(lines[len(lines)-n:], "\n")
-}
diff --git a/dashboard/app/build/ui.html b/dashboard/app/build/ui.html
deleted file mode 100644
index 6ae268c..0000000
--- a/dashboard/app/build/ui.html
+++ /dev/null
@@ -1,210 +0,0 @@
-<!DOCTYPE HTML>
-<html>
- <head>
- <title>{{$.Dashboard.Name}} Build Dashboard</title>
- <link rel="stylesheet" href="/static/style.css"/>
- <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
- <script>
- var showUnsupported = window.location.hash.substr(1) != "short";
- function redraw() {
- showUnsupported = !$("#showshort").prop('checked');
- $('.unsupported')[showUnsupported?'show':'hide']();
- window.location.hash = showUnsupported?'':'short';
- }
- $(document).ready(function() {
- $("#showshort").attr('checked', !showUnsupported).change(redraw);
- redraw();
- })
- </script>
- </head>
-
- <body>
- <header id="topbar">
- <h1>Go Dashboard</h1>
- <nav>
- <a href="{{$.Dashboard.RelPath}}">Test</a>
- <a href="{{$.Dashboard.RelPath}}perf">Perf</a>
- <a href="{{$.Dashboard.RelPath}}perfgraph">Graphs</a>
- </nav>
- <div class="clear"></div>
- </header>
-
- <nav class="dashboards">
- {{range buildDashboards}}
- <a href="{{.RelPath}}">{{.Name}}</a>
- {{end}}
- <label>
- <input type=checkbox id="showshort">
- show only <a href="http://golang.org/wiki/PortingPolicy">first-class ports</a>
- </label>
- </nav>
- {{with $.Package.Name}}<h2>{{.}}</h2>{{end}}
-
- <div class="page">
-
- {{if $.Commits}}
-
- <table class="build">
- <colgroup class="col-hash" {{if $.Package.Path}}span="2"{{end}}></colgroup>
- {{range $.Builders | builderSpans}}
- <colgroup class="col-result{{if .Unsupported}} unsupported{{end}}" span="{{.N}}"></colgroup>
- {{end}}
- <colgroup class="col-user"></colgroup>
- <colgroup class="col-time"></colgroup>
- <colgroup class="col-desc"></colgroup>
- <tr>
- <!-- extra row to make alternating colors use dark for first result -->
- </tr>
- <tr>
- {{if $.Package.Path}}
- <th colspan="2">revision</th>
- {{else}}
- <th>&nbsp;</th>
- {{end}}
- {{range $.Builders | builderSpans}}
- <th {{if .Unsupported}}class="unsupported"{{end}} colspan="{{.N}}">{{.OS}}</th>
- {{end}}
- <th></th>
- <th></th>
- <th></th>
- </tr>
- <tr>
- {{if $.Package.Path}}
- <th class="result arch">repo</th>
- <th class="result arch">{{$.Dashboard.Name}}</th>
- {{else}}
- <th>&nbsp;</th>
- {{end}}
- {{range $.Builders}}
- <th class="result arch{{if (unsupported .)}} unsupported{{end}}" title="{{.}}">{{builderSubheading .}}</th>
- {{end}}
- <th></th>
- <th></th>
- <th></th>
- </tr>
- {{range $c := $.Commits}}
- {{range $i, $h := $c.ResultGoHashes}}
- <tr class="commit">
- {{if $i}}
- <td>&nbsp;</td>
- {{else}}
- <td class="hash"><a href="{{repoURL $.Dashboard.Name $c.Hash $.Package.Path}}">{{shortHash $c.Hash}}</a></td>
- {{end}}
- {{if $h}}
- <td class="hash"><a href="{{repoURL $.Dashboard.Name $h ""}}">{{shortHash $h}}</a></td>
- {{end}}
- {{range $.Builders}}
- <td class="result{{if (unsupported .)}} unsupported{{end}}">
- {{with $c.Result . $h}}
- {{if .OK}}
- <span class="ok">ok</span>
- {{else}}
- <a href="{{$.Dashboard.RelPath}}log/{{.LogHash}}" class="fail">fail</a>
- {{end}}
- {{else}}
- &nbsp;
- {{end}}
- </td>
- {{end}}
- {{if $i}}
- <td>&nbsp;</td>
- <td>&nbsp;</td>
- <td>&nbsp;</td>
- {{else}}
- <td class="user" title="{{$c.User}}">{{shortUser $c.User}}</td>
- <td class="time">{{$c.Time.Format "Mon 02 Jan 15:04"}}</td>
- <td class="desc" title="{{$c.Desc}}">{{shortDesc $c.Desc}}</td>
- {{end}}
- </tr>
- {{end}}
- {{end}}
- </table>
-
- {{with $.Pagination}}
- <div class="paginate">
- <nav>
- <a {{if .HasPrev}}href="?{{with $.Package.Path}}repo={{.}}&{{end}}page={{.Prev}}{{with $.Branch}}&branch={{.}}{{end}}"{{else}}class="inactive"{{end}}>newer</a>
- <a {{if .Next}}href="?{{with $.Package.Path}}repo={{.}}&{{end}}page={{.Next}}{{with $.Branch}}&branch={{.}}{{end}}"{{else}}class="inactive"{{end}}>older</a>
- <a {{if .HasPrev}}href=".{{with $.Branch}}?branch={{.}}{{end}}"{{else}}class="inactive"{{end}}>latest</a>
- </nav>
- </div>
- {{end}}
-
- {{else}}
- <p>No commits to display. Hm.</p>
- {{end}}
-
- {{with $.TipState}}
- {{$goHash := .Tag.Hash}}
- {{if .Packages}}
- <h2>
- Sub-repositories at tip
- <small>(<a href="{{repoURL $.Dashboard.Name .Tag.Hash ""}}">{{shortHash .Tag.Hash}}</a>)</small>
- </h2>
-
- <table class="build">
- <colgroup class="col-package"></colgroup>
- <colgroup class="col-hash"></colgroup>
- {{range $.Builders | builderSpans}}
- <colgroup class="col-result{{if .Unsupported}} unsupported{{end}}" span="{{.N}}"></colgroup>
- {{end}}
- <colgroup class="col-user"></colgroup>
- <colgroup class="col-time"></colgroup>
- <colgroup class="col-desc"></colgroup>
- <tr>
- <!-- extra row to make alternating colors use dark for first result -->
- </tr>
- <tr>
- <th></th>
- <th></th>
- {{range $.Builders | builderSpans}}
- <th {{if .Unsupported}}class="unsupported"{{end}} colspan="{{.N}}">{{.OS}}</th>
- {{end}}
- <th></th>
- <th></th>
- <th></th>
- </tr>
- <tr>
- <th></th>
- <th></th>
- {{range $.Builders}}
- <th class="result arch{{if (unsupported .)}} unsupported{{end}}" title="{{.}}">{{builderSubheading .}}</th>
- {{end}}
- <th></th>
- <th></th>
- <th></th>
- </tr>
- {{range $pkg := .Packages}}
- <tr class="commit">
- <td><a title="{{.Package.Path}}" href="?repo={{.Package.Path}}">{{.Package.Name}}</a></td>
- <td class="hash">
- {{$h := $pkg.Commit.Hash}}
- <a href="{{repoURL $.Dashboard.Name $h $pkg.Commit.PackagePath}}">{{shortHash $h}}</a>
- </td>
- {{range $.Builders}}
- <td class="result{{if (unsupported .)}} unsupported{{end}}">
- {{with $pkg.Commit.Result . $goHash}}
- {{if .OK}}
- <span class="ok">ok</span>
- {{else}}
- <a href="{{$.Dashboard.RelPath}}log/{{.LogHash}}" class="fail">fail</a>
- {{end}}
- {{else}}
- &nbsp;
- {{end}}
- </td>
- {{end}}
- {{with $pkg.Commit}}
- <td class="user" title="{{.User}}">{{shortUser .User}}</td>
- <td class="time">{{.Time.Format "Mon 02 Jan 15:04"}}</td>
- <td class="desc" title="{{.Desc}}">{{shortDesc .Desc}}</td>
- {{end}}
- </tr>
- {{end}}
- </table>
- {{end}}
- {{end}}
-
- </div>
- </body>
-</html>
diff --git a/dashboard/app/build/update.go b/dashboard/app/build/update.go
deleted file mode 100644
index 1d22cc9..0000000
--- a/dashboard/app/build/update.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build appengine
-
-package build
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
-
- "appengine"
- "appengine/datastore"
-)
-
-func init() {
- http.HandleFunc("/updatebenchmark", updateBenchmark)
-}
-
-func updateBenchmark(w http.ResponseWriter, r *http.Request) {
- if !appengine.IsDevAppServer() {
- fmt.Fprint(w, "Update must not run on real server.")
- return
- }
-
- if r.Method != "POST" {
- fmt.Fprintf(w, "bad request method")
- return
- }
-
- c := contextForRequest(r)
- if !validKey(c, r.FormValue("key"), r.FormValue("builder")) {
- fmt.Fprintf(w, "bad builder/key")
- return
- }
-
- defer r.Body.Close()
- var hashes []string
- if err := json.NewDecoder(r.Body).Decode(&hashes); err != nil {
- fmt.Fprintf(w, "failed to decode request: %v", err)
- return
- }
-
- ncommit := 0
- nrun := 0
- tx := func(c appengine.Context) error {
- var cr *CommitRun
- for _, hash := range hashes {
- // Update Commit.
- com := &Commit{Hash: hash}
- err := datastore.Get(c, com.Key(c), com)
- if err != nil && err != datastore.ErrNoSuchEntity {
- return fmt.Errorf("fetching Commit: %v", err)
- }
- if err == datastore.ErrNoSuchEntity {
- continue
- }
- com.NeedsBenchmarking = true
- com.PerfResults = nil
- if err := putCommit(c, com); err != nil {
- return err
- }
- ncommit++
-
- // create PerfResult
- res := &PerfResult{CommitHash: com.Hash, CommitNum: com.Num}
- err = datastore.Get(c, res.Key(c), res)
- if err != nil && err != datastore.ErrNoSuchEntity {
- return fmt.Errorf("fetching PerfResult: %v", err)
- }
- if err == datastore.ErrNoSuchEntity {
- if _, err := datastore.Put(c, res.Key(c), res); err != nil {
- return fmt.Errorf("putting PerfResult: %v", err)
- }
- }
-
- // Update CommitRun.
- if cr != nil && cr.StartCommitNum != com.Num/PerfRunLength*PerfRunLength {
- if _, err := datastore.Put(c, cr.Key(c), cr); err != nil {
- return fmt.Errorf("putting CommitRun: %v", err)
- }
- nrun++
- cr = nil
- }
- if cr == nil {
- var err error
- cr, err = GetCommitRun(c, com.Num)
- if err != nil {
- return fmt.Errorf("getting CommitRun: %v", err)
- }
- }
- if com.Num < cr.StartCommitNum || com.Num >= cr.StartCommitNum+PerfRunLength {
- return fmt.Errorf("commit num %v out of range [%v, %v)", com.Num, cr.StartCommitNum, cr.StartCommitNum+PerfRunLength)
- }
- idx := com.Num - cr.StartCommitNum
- cr.Hash[idx] = com.Hash
- cr.User[idx] = shortDesc(com.User)
- cr.Desc[idx] = shortDesc(com.Desc)
- cr.Time[idx] = com.Time
- cr.NeedsBenchmarking[idx] = com.NeedsBenchmarking
- }
- if cr != nil {
- if _, err := datastore.Put(c, cr.Key(c), cr); err != nil {
- return fmt.Errorf("putting CommitRun: %v", err)
- }
- nrun++
- }
- return nil
- }
- if err := datastore.RunInTransaction(c, tx, nil); err != nil {
- fmt.Fprintf(w, "failed to execute tx: %v", err)
- return
- }
- fmt.Fprintf(w, "OK (updated %v commits and %v commit runs)", ncommit, nrun)
-}