diff options
Diffstat (limited to 'dashboard/app/build')
-rw-r--r-- | dashboard/app/build/build.go | 911 | ||||
-rw-r--r-- | dashboard/app/build/dash.go | 118 | ||||
-rw-r--r-- | dashboard/app/build/handler.go | 906 | ||||
-rw-r--r-- | dashboard/app/build/init.go | 46 | ||||
-rw-r--r-- | dashboard/app/build/notify.go | 378 | ||||
-rw-r--r-- | dashboard/app/build/notify.txt | 9 | ||||
-rw-r--r-- | dashboard/app/build/perf.go | 312 | ||||
-rw-r--r-- | dashboard/app/build/perf_changes.go | 282 | ||||
-rw-r--r-- | dashboard/app/build/perf_changes.html | 89 | ||||
-rw-r--r-- | dashboard/app/build/perf_detail.go | 221 | ||||
-rw-r--r-- | dashboard/app/build/perf_detail.html | 101 | ||||
-rw-r--r-- | dashboard/app/build/perf_graph.go | 270 | ||||
-rw-r--r-- | dashboard/app/build/perf_graph.html | 120 | ||||
-rw-r--r-- | dashboard/app/build/perf_learn.go | 186 | ||||
-rw-r--r-- | dashboard/app/build/perf_learn.html | 45 | ||||
-rw-r--r-- | dashboard/app/build/perf_notify.txt | 13 | ||||
-rw-r--r-- | dashboard/app/build/test.go | 378 | ||||
-rw-r--r-- | dashboard/app/build/ui.go | 460 | ||||
-rw-r--r-- | dashboard/app/build/ui.html | 210 | ||||
-rw-r--r-- | dashboard/app/build/update.go | 117 |
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"> </td> - <td class="numresults"> </td> - {{end}} - <td>{{$m.Name}}</td> - <td> - {{range $ch := $m.BadChanges}} - <a class="{{$ch.Style}}" href="{{$ch.Link}}" title="{{$ch.Hint}}">{{$ch.Val}}</a> - {{end}} - </td> - <td> - {{range $ch := $m.GoodChanges}} - <a class="{{$ch.Style}}" href="{{$ch.Link}}" title="{{$ch.Hint}}">{{$ch.Val}}</a> - {{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"> </td> - <td class="time"> </td> - <td class="desc"> </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> </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> </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> </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}} - - {{end}} - </td> - {{end}} - {{if $i}} - <td> </td> - <td> </td> - <td> </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}} - - {{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) -} |