Chromium Code Reviews| Index: appengine/cmd/milo/buildbot/console.go |
| diff --git a/appengine/cmd/milo/buildbot/console.go b/appengine/cmd/milo/buildbot/console.go |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..bcd748b4c2a2b7501f3c94a3fec080c8f2250e02 |
| --- /dev/null |
| +++ b/appengine/cmd/milo/buildbot/console.go |
| @@ -0,0 +1,111 @@ |
| +// 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" |
| + "time" |
| + |
| + "github.com/luci/gae/service/datastore" |
| + "github.com/luci/luci-go/appengine/cmd/milo/resp" |
| + "github.com/luci/luci-go/common/clock" |
| + log "github.com/luci/luci-go/common/logging" |
| + "github.com/luci/luci-go/common/sync/parallel" |
| + "golang.org/x/net/context" |
| +) |
| + |
| +// getBuilds fetches all of the recent builds from the datastore. |
| +func getFullBuilds(c context.Context, masterName, builderName string, finished bool) ([]*buildbotBuild, error) { |
|
nodir
2016/08/03 20:26:16
why "Full"?
hinoka
2016/08/03 21:55:39
Not sure, I think it was arbitrary. I guess full
|
| + // TODO(hinoka): Builder specific structs. |
| + ds := datastore.Get(c) |
| + q := datastore.NewQuery("buildbotBuild") |
| + q = q.Eq("finished", finished) |
| + q = q.Eq("master", masterName) |
| + q = q.Eq("builder", builderName) |
| + q = q.Limit(25) // TODO(hinoka): This should be adjustable |
| + q = q.Order("-number") |
| + q.Finalize() |
| + buildbots := make([]*buildbotBuild, 0, 25) |
| + err := ds.GetAll(q, &buildbots) |
| + return buildbots, err |
| +} |
| + |
| +func GetConsoleBuilds( |
| + c context.Context, builders []resp.BuilderRef, commits []string) ( |
| + [][]*resp.ConsoleBuild, error) { |
| + |
| + // First, fetch all the master entries referenced. |
| + // TODO(hinoka): Parallel fetch. |
| + masters := map[string]*buildbotMaster{} |
| + for _, builder := range builders { |
|
nodir
2016/08/03 20:26:17
this loop may take a lot of time because of master
hinoka
2016/08/03 21:55:39
Actually this entire loop isn't used... I forgot w
|
| + f := strings.SplitN(builder.Name, "/", 2) |
|
nodir
2016/08/03 20:26:17
validate that len(f) == 2, return error if not so
hinoka
2016/08/03 21:55:39
Deleted
|
| + masterName := f[0] |
| + if builder.Module != "buildbot" { |
| + panic(fmt.Errorf("Module %s is not buildbot", builder.Module)) |
| + } |
| + if _, ok := masters[masterName]; !ok { |
| + master, internal, t, err := getMasterJSON(c, masterName) |
| + if err != nil { |
| + return nil, err |
| + } |
| + if t.Before(clock.Now(c).Add(-time.Minute * 5)) { |
| + return nil, fmt.Errorf("Data for master is outdated, last updated %s", t) |
| + } |
| + if internal { |
| + return nil, fmt.Errorf("Internal masters not supported") |
|
nodir
2016/08/03 20:26:17
if someone defines a config that references an int
hinoka
2016/08/03 21:55:39
Acknowledged. Probably should just treat the build
|
| + } |
| + masters[masterName] = master |
| + } |
| + } |
| + |
| + results := make([][]*resp.ConsoleBuild, len(commits)) |
| + for i := range results { |
| + results[i] = make([]*resp.ConsoleBuild, len(builders)) |
| + } |
| + // Reuse the builder code. 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 // How do scopes werk |
| + f := strings.SplitN(builder.Name, "/", 2) |
| + master := f[0] |
| + builderName := f[1] |
|
nodir
2016/08/03 20:26:16
this will panic if builder.Name does not have a sl
hinoka
2016/08/03 21:55:39
Done.
|
| + taskC <- func() error { |
| + t1 := clock.Now(c) |
| + builds, err := getFullBuilds(c, master, builderName, true) |
|
nodir
2016/08/03 20:26:16
it won't show running builds? also does not show a
hinoka
2016/08/03 21:55:39
Yes that is true, but it hasn't been a problem for
nodir
2016/08/04 23:13:12
ok, makes sense now
|
| + if err != nil { |
| + return err |
| + } |
| + t2 := clock.Now(c) |
| + var currentStatus *resp.Status |
| + for j, commit := range commits { |
| + for _, build := range builds { |
| + if build.Sourcestamp.Revision == commit { |
|
nodir
2016/08/03 20:26:17
this is O(n^2)
make a map commit->j before fanOutI
hinoka
2016/08/03 21:55:39
Logic for filling in-between builds on L97 would b
|
| + results[j][i] = &resp.ConsoleBuild{ |
|
nodir
2016/08/03 20:26:17
this code is racy: it mutates results[j] in differ
hinoka
2016/08/03 21:55:39
Each goroutine fully owns a space in [i], so I don
nodir
2016/08/04 23:13:12
Acknowledged.
|
| + Link: &resp.Link{ |
| + Label: strings.Join(build.Text, " "), |
| + URL: 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} |
|
nodir
2016/08/03 20:26:17
I don't understand this logic. It might be simpler
hinoka
2016/08/03 21:55:39
Each goroutine processes a single builder. This j
nodir
2016/08/04 23:13:12
this does not sound correct. if there was no build
|
| + } |
| + } |
| + 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 |
| + |
|
nodir
2016/08/03 20:26:17
remove blank line
hinoka
2016/08/03 21:55:39
Done.
|
| +} |