diff --git a/dashboard/app/app.yaml b/dashboard/app/app.yaml index a71abcee7f..35fb75b85a 100644 --- a/dashboard/app/app.yaml +++ b/dashboard/app/app.yaml @@ -13,7 +13,7 @@ handlers: static_dir: static - url: /(|gccgo/|hg/)log/.+ script: _go_app -- url: /(|gccgo/|hg/)(|clear-results|commit|packages|result|perf-result|tag|todo|perf|perfdetail|perfgraph|updatebenchmark) +- url: /(|gccgo/|hg/)(|building|clear-results|commit|packages|result|perf-result|tag|todo|perf|perfdetail|perfgraph|updatebenchmark) script: _go_app - url: /(|gccgo/|hg/)(init|buildtest|key|perflearn|_ah/queue/go/delay) script: _go_app diff --git a/dashboard/app/build/build.go b/dashboard/app/build/build.go index 11c3262fe9..3b52a7c025 100644 --- a/dashboard/app/build/build.go +++ b/dashboard/app/build/build.go @@ -78,6 +78,10 @@ func GetPackage(c appengine.Context, path string) (*Package, error) { return p, err } +type builderAndGoHash struct { + builder, goHash string +} + // A Commit describes an individual commit in a package. // // Each Commit entity is a descendant of its associated Package entity. @@ -107,6 +111,8 @@ type Commit struct { PerfResults []string `datastore:",noindex"` FailNotificationSent bool + + buildingURLs map[builderAndGoHash]string } func (com *Commit) Key(c appengine.Context) *datastore.Key { @@ -216,12 +222,24 @@ func min(a, b int) int { // 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 { + if !strings.HasPrefix(r, builder) { + // Avoid strings.SplitN alloc in the common case. + continue + } p := strings.SplitN(r, "|", 4) if len(p) != 4 || p[0] != builder || p[3] != goHash { continue } return partsToResult(c, p) } + if u, ok := c.buildingURLs[builderAndGoHash{builder, goHash}]; ok { + return &Result{ + Builder: builder, + BuildingURL: u, + Hash: c.Hash, + GoHash: goHash, + } + } return nil } @@ -409,9 +427,10 @@ type Result struct { // 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. + BuildingURL string `datastore:"-"` // non-empty if currently building + 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 } diff --git a/dashboard/app/build/handler.go b/dashboard/app/build/handler.go index e58b33c962..5b7c7cb212 100644 --- a/dashboard/app/build/handler.go +++ b/dashboard/app/build/handler.go @@ -17,10 +17,12 @@ import ( "net/http" "strconv" "strings" + "time" "unicode/utf8" "appengine" "appengine/datastore" + "appengine/memcache" "cache" "key" @@ -516,6 +518,29 @@ func packagesHandler(r *http.Request) (interface{}, error) { return p, nil } +// buildingHandler records that a build is in progress. +// The data is only stored in memcache and with a timeout. It's assumed +// that the build system will periodically refresh this if the build +// is slow. +func buildingHandler(r *http.Request) (interface{}, error) { + if r.Method != "POST" { + return nil, errBadMethod(r.Method) + } + c := contextForRequest(r) + key := fmt.Sprintf("building|%s|%s|%s", r.FormValue("hash"), r.FormValue("gohash"), r.FormValue("builder")) + err := memcache.Set(c, &memcache.Item{ + Key: key, + Value: []byte(r.FormValue("url")), + Expiration: 15 * time.Minute, + }) + if err != nil { + return nil, err + } + return map[string]interface{}{ + "key": key, + }, 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. @@ -909,6 +934,7 @@ func init() { handleFunc("/key", keyHandler) // authenticated handlers + handleFunc("/building", AuthHandler(buildingHandler)) handleFunc("/clear-results", AuthHandler(clearResultsHandler)) handleFunc("/commit", AuthHandler(commitHandler)) handleFunc("/packages", AuthHandler(packagesHandler)) diff --git a/dashboard/app/build/ui.go b/dashboard/app/build/ui.go index e62380ffc1..76be9303e3 100644 --- a/dashboard/app/build/ui.go +++ b/dashboard/app/build/ui.go @@ -27,6 +27,7 @@ import ( "appengine" "appengine/datastore" + "appengine/memcache" ) func init() { @@ -97,6 +98,7 @@ func uiHandler(w http.ResponseWriter, r *http.Request) { p.HasPrev = true } data := &uiTemplateData{d, pkg, commits, builders, tipState, p, branch} + data.populateBuildingURLs(c) switch r.FormValue("mode") { case "failures": @@ -376,6 +378,54 @@ type uiTemplateData struct { Branch string } +// populateBuildingURLs populates each commit in Commits' buildingURLs map with the +// URLs of builds which are currently in progress. +func (td *uiTemplateData) populateBuildingURLs(ctx appengine.Context) { + // need are memcache keys: "building|||" + // The hash is of the main "go" repo, or the subrepo commit hash. + // The gohash is empty for the main repo, else it's the Go hash. + var need []string + + commit := map[string]*Commit{} // commit hash -> Commit + + // TODO(bradfitz): this only populates the main repo, not subpackages currently. + for _, b := range td.Builders { + for _, c := range td.Commits { + if c.Result(b, "") == nil { + commit[c.Hash] = c + need = append(need, "building|"+c.Hash+"||"+b) + } + } + } + if len(need) == 0 { + return + } + m, err := memcache.GetMulti(ctx, need) + if err != nil { + // oh well. this is a cute non-critical feature anyway. + ctx.Debugf("GetMulti of building keys: %v", err) + return + } + for k, it := range m { + f := strings.SplitN(k, "|", 4) + if len(f) != 4 { + continue + } + hash, goHash, builder := f[1], f[2], f[3] + c, ok := commit[hash] + if !ok { + continue + } + m := c.buildingURLs + if m == nil { + m = make(map[builderAndGoHash]string) + c.buildingURLs = m + } + m[builderAndGoHash{builder, goHash}] = string(it.Value) + } + +} + var uiTemplate = template.Must( template.New("ui.html").Funcs(tmplFuncs).ParseFiles("build/ui.html"), ) diff --git a/dashboard/app/build/ui.html b/dashboard/app/build/ui.html index 01b8c08575..d885e1c3b6 100644 --- a/dashboard/app/build/ui.html +++ b/dashboard/app/build/ui.html @@ -96,7 +96,9 @@ {{range $.Builders}} {{with $c.Result . $h}} - {{if .OK}} + {{if .BuildingURL}} + + {{else if .OK}} ok {{else}} fail