| // Copyright 2016 The LUCI Authors. All rights reserved. |
| // Use of this source code is governed under the Apache License, Version 2.0 |
| // that can be found in the LICENSE file. |
| |
| package buildbot |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| ds "github.com/luci/gae/service/datastore" |
| "github.com/luci/luci-go/common/clock" |
| log "github.com/luci/luci-go/common/logging" |
| "github.com/luci/luci-go/common/sync/parallel" |
| "github.com/luci/luci-go/milo/api/resp" |
| "github.com/luci/luci-go/milo/appengine/common/model" |
| |
| "golang.org/x/net/context" |
| ) |
| |
| // getFullBuilds fetches all of the recent builds from the datastore. |
| func getFullBuilds(c context.Context, masterName, builderName string, finished bool) ([]*buildbotBuild, error) { |
| // TODO(hinoka): Builder specific structs. |
| q := ds.NewQuery("buildbotBuild") |
| q = q.Eq("finished", finished) |
| q = q.Eq("master", masterName) |
| q = q.Eq("builder", builderName) |
| q = q.Order("-number") |
| q.Finalize() |
| // Ignore the cursor, we don't need it. |
| buildbots, _, err := runBuildsQuery(c, q, 25) |
| return buildbots, err |
| } |
| |
| // GetConsoleBuilds takes commits and builders and returns a matrix of |
| // resp.ConsoleBuild objects. The expected format of the result |
| // is [len(commits)][len(builders)]*resp.ConsoleBuild. The first level matches |
| // 1:1 to a commit, and the second level matches 1:1 to a builder. |
| func GetConsoleBuilds( |
| c context.Context, builders []resp.BuilderRef, commits []string) ( |
| [][]*resp.ConsoleBuild, error) { |
| |
| results := make([][]*resp.ConsoleBuild, len(commits)) |
| for i := range results { |
| results[i] = make([]*resp.ConsoleBuild, len(builders)) |
| } |
| // HACK(hinoka): This fetches 25 full builds and then filters them. Replace this |
| // with something more reasonable. |
| // This is kind of a hack but it's okay for now. |
| err := parallel.FanOutIn(func(taskC chan<- func() error) { |
| for i, builder := range builders { |
| i := i |
| builder := builder |
| builderComponents := strings.SplitN(builder.Name, "/", 2) |
| if len(builderComponents) != 2 { |
| taskC <- func() error { |
| return fmt.Errorf("%s is an invalid builder name", builder.Name) |
| } |
| return |
| } |
| master := builderComponents[0] |
| builderName := builderComponents[1] |
| taskC <- func() error { |
| t1 := clock.Now(c) |
| builds, err := getFullBuilds(c, master, builderName, true) |
| if err != nil { |
| return err |
| } |
| t2 := clock.Now(c) |
| var currentStatus *model.Status |
| for j, commit := range commits { |
| for _, build := range builds { |
| if build.Sourcestamp.Revision == commit { |
| results[j][i] = &resp.ConsoleBuild{ |
| Link: resp.NewLink( |
| strings.Join(build.Text, " "), |
| fmt.Sprintf("/buildbot/%s/%s/%d", master, builderName, build.Number), |
| ), |
| Status: build.toStatus(), |
| } |
| currentStatus = &results[j][i].Status |
| } |
| } |
| if currentStatus != nil && results[j][i] == nil { |
| results[j][i] = &resp.ConsoleBuild{Status: *currentStatus} |
| } |
| } |
| log.Debugf(c, |
| "Builder %s took %s to query, %s to compute.", builderName, |
| t2.Sub(t1), clock.Since(c, t2)) |
| return nil |
| } |
| } |
| }) |
| return results, err |
| } |