aboutsummaryrefslogtreecommitdiff
path: root/dashboard/app/build/notify.go
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard/app/build/notify.go')
-rw-r--r--dashboard/app/build/notify.go378
1 files changed, 0 insertions, 378 deletions
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)
- }
-}