app/appengine: do maintner & coordinator RPCs concurrently
Fixes a TODO.
The coordinator RPC should be temporary, but a) who ever knows how
long temporary is, and b) it's easy enough to do, and c) faster is
nicer.
Updates golang/go#34744
Change-Id: Id7216e1c67dc04152565bb8c5d6181cde674a12e
Reviewed-on: https://go-review.googlesource.com/c/build/+/210997
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/app/appengine/ui.go b/app/appengine/ui.go
index 6316d40..e731736 100644
--- a/app/appengine/ui.go
+++ b/app/appengine/ui.go
@@ -25,6 +25,7 @@
"golang.org/x/build/maintner/maintnerd/apipb"
"golang.org/x/build/repos"
"golang.org/x/build/types"
+ "golang.org/x/sync/errgroup"
"grpc.go4.org"
"grpc.go4.org/codes"
)
@@ -43,18 +44,34 @@
return
}
- // TODO: if we end up fetching the building state from the
- // coordinator too, do that concurrently in an
- // x/sync/errgroup.Group here. But for now we're only doing
- // one RPC call.
ctx := r.Context()
- dashRes, err := maintnerClient.GetDashboard(ctx, dashReq)
- if err != nil {
+ var (
+ dashRes *apipb.DashboardResponse
+ activeBuilds []types.ActivePostSubmitBuild
+ )
+ var rpcs errgroup.Group
+ rpcs.Go(func() error {
+ var err error
+ dashRes, err = maintnerClient.GetDashboard(ctx, dashReq)
+ return err
+ })
+ if view.ShowsActiveBuilds() {
+ rpcs.Go(func() error {
+ activeBuilds = getActiveBuilds(ctx)
+ return nil
+ })
+ }
+ if err := rpcs.Wait(); err != nil {
http.Error(w, "maintner.GetDashboard: "+err.Error(), httpStatusOfErr(err))
return
}
- tb := newUITemplateDataBuilder(view, dashReq, dashRes)
+ tb := &uiTemplateDataBuilder{
+ view: view,
+ req: dashReq,
+ res: dashRes,
+ activeBuilds: activeBuilds,
+ }
data, err := tb.buildTemplateData(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -67,6 +84,9 @@
// See viewForRequest.
type dashboardView interface {
ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData)
+ // ShowsActiveBuilds reports whether this view uses
+ // information about the currently active builds.
+ ShowsActiveBuilds() bool
}
// viewForRequest selects the dashboardView based on the HTTP
@@ -96,9 +116,10 @@
// dashboardViews. That is, it maps the maintner protobuf response to
// the data structure needed by the dashboardView/template.
type uiTemplateDataBuilder struct {
- view dashboardView
- req *apipb.DashboardRequest
- res *apipb.DashboardResponse
+ view dashboardView
+ req *apipb.DashboardRequest
+ res *apipb.DashboardResponse
+ activeBuilds []types.ActivePostSubmitBuild // optional; for blue gopher links
// testCommitData, if non-nil, provides an alternate data
// source to use for testing instead of making real datastore
@@ -106,17 +127,6 @@
testCommitData map[string]*Commit
}
-// newUITemplateDataBuilder returns a new uiTemplateDataBuilder for a
-// given view, dashboard request, and dashboard response from
-// maintner.
-func newUITemplateDataBuilder(view dashboardView, req *apipb.DashboardRequest, res *apipb.DashboardResponse) *uiTemplateDataBuilder {
- return &uiTemplateDataBuilder{
- view: view,
- req: req,
- res: res,
- }
-}
-
// getCommitsToLoad returns a set (all values are true) of which commits to load from
// the datastore.
func (tb *uiTemplateDataBuilder) getCommitsToLoad() map[commitInPackage]bool {
@@ -324,9 +334,10 @@
if tb.req.Repo == "" && tb.req.Branch == "master" {
data.Builders = onlyGoMasterBuilders(data.Builders)
}
-
+ }
+ if tb.view.ShowsActiveBuilds() {
// Populate building URLs for the HTML UI only.
- data.populateBuildingURLs(ctx)
+ data.populateBuildingURLs(ctx, tb.activeBuilds)
}
return data, nil
@@ -335,6 +346,7 @@
// htmlView renders the HTML (default) form of https://build.golang.org/ with no mode parameter.
type htmlView struct{}
+func (htmlView) ShowsActiveBuilds() bool { return true }
func (htmlView) ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData) {
var buf bytes.Buffer
if err := uiTemplate.Execute(&buf, data); err != nil {
@@ -398,6 +410,7 @@
// hash builder failure-url
type failuresView struct{}
+func (failuresView) ShowsActiveBuilds() bool { return false }
func (failuresView) ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData) {
w.Header().Set("Content-Type", "text/plain")
for _, c := range data.Commits {
@@ -416,6 +429,7 @@
// The output is a types.BuildStatus JSON object.
type jsonView struct{}
+func (jsonView) ShowsActiveBuilds() bool { return false }
func (jsonView) ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData) {
// cell returns one of "" (no data), "ok", or a failure URL.
cell := func(res *Result) string {
@@ -708,44 +722,38 @@
Branch string
}
-var altActiveBuildingForTest func() []types.ActivePostSubmitBuild
-
-// getActiveBuilding returns the builds that coordinator is currently doing.
+// getActiveBuilds returns the builds that coordinator is currently doing.
// This isn't critical functionality so errors are logged but otherwise ignored for now.
// Once this is merged into the coordinator we won't need to make an RPC to get
// this info. See https://github.com/golang/go/issues/34744#issuecomment-563398753.
-func getActiveBuilding(ctx context.Context) (builds []types.ActivePostSubmitBuild) {
- if f := altActiveBuildingForTest; f != nil {
- return f()
- }
+func getActiveBuilds(ctx context.Context) (builds []types.ActivePostSubmitBuild) {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
req, _ := http.NewRequest("GET", "https://farmer.golang.org/status/post-submit-active.json", nil)
req = req.WithContext(ctx)
res, err := http.DefaultClient.Do(req)
if err != nil {
- log.Printf("getActiveBuilding: Do: %v", err)
+ log.Printf("getActiveBuilds: Do: %v", err)
return
}
defer res.Body.Close()
if res.StatusCode != 200 {
- log.Printf("getActiveBuilding: %v", res.Status)
+ log.Printf("getActiveBuilds: %v", res.Status)
return
}
if err := json.NewDecoder(res.Body).Decode(&builds); err != nil {
- log.Printf("getActiveBuilding: JSON decode: %v", err)
+ log.Printf("getActiveBuilds: JSON decode: %v", err)
}
return builds
}
// populateBuildingURLs populates each commit in Commits' buildingURLs map with the
// URLs of builds which are currently in progress.
-func (td *uiTemplateData) populateBuildingURLs(ctx context.Context) {
- activeBuilding := getActiveBuilding(ctx)
+func (td *uiTemplateData) populateBuildingURLs(ctx context.Context, activeBuilds []types.ActivePostSubmitBuild) {
// active maps from a build record with its status URL zeroed
// out to to the actual value of that status URL.
active := map[types.ActivePostSubmitBuild]string{}
- for _, rec := range activeBuilding {
+ for _, rec := range activeBuilds {
statusURL := rec.StatusURL
rec.StatusURL = ""
active[rec] = statusURL
diff --git a/app/appengine/ui_test.go b/app/appengine/ui_test.go
index f5658e1..5ff7072 100644
--- a/app/appengine/ui_test.go
+++ b/app/appengine/ui_test.go
@@ -20,10 +20,7 @@
// Thin the list of builders to make this test's data lighter
// and require less maintenance keeping it in sync.
origBuilders := dashboard.Builders
- defer func() {
- dashboard.Builders = origBuilders
- altActiveBuildingForTest = nil
- }()
+ defer func() { dashboard.Builders = origBuilders }()
dashboard.Builders = map[string]*dashboard.BuildConfig{
"linux-amd64": origBuilders["linux-amd64"],
"linux-386": origBuilders["linux-386"],
@@ -315,12 +312,12 @@
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- altActiveBuildingForTest = func() (builds []types.ActivePostSubmitBuild) {
- return tt.activeBuilds
- }
- tb := newUITemplateDataBuilder(tt.view, tt.req, tt.res)
- if tt.testCommitData != nil {
- tb.testCommitData = tt.testCommitData
+ tb := &uiTemplateDataBuilder{
+ view: tt.view,
+ req: tt.req,
+ res: tt.res,
+ activeBuilds: tt.activeBuilds,
+ testCommitData: tt.testCommitData,
}
data, err := tb.buildTemplateData(context.Background())
if err != nil {