Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(542)

Side by Side Diff: milo/appengine/buildbot/builder.go

Issue 2810113002: Milo buildbot builder page: Add pagnation with cursors. (Closed)
Patch Set: train Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « milo/api/resp/builder.go ('k') | milo/appengine/buildbot/console.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2016 The LUCI Authors. All rights reserved. 1 // Copyright 2016 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0 2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file. 3 // that can be found in the LICENSE file.
4 4
5 package buildbot 5 package buildbot
6 6
7 import ( 7 import (
8 "crypto/sha1"
9 "encoding/base64"
8 "errors" 10 "errors"
9 "fmt" 11 "fmt"
10 "sort" 12 "sort"
11 "strings" 13 "strings"
12 "time" 14 "time"
13 15
14 "github.com/luci/gae/service/datastore" 16 "github.com/luci/gae/service/datastore"
17 "github.com/luci/gae/service/memcache"
15 18
16 "github.com/luci/luci-go/common/clock" 19 "github.com/luci/luci-go/common/clock"
17 "github.com/luci/luci-go/common/logging" 20 "github.com/luci/luci-go/common/logging"
18 "github.com/luci/luci-go/milo/api/resp" 21 "github.com/luci/luci-go/milo/api/resp"
19 "golang.org/x/net/context" 22 "golang.org/x/net/context"
20 ) 23 )
21 24
22 // builderRef is used for keying specific builds in a master json. 25 // builderRef is used for keying specific builds in a master json.
23 type builderRef struct { 26 type builderRef struct {
24 builder string 27 builder string
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
78 }, 81 },
79 Text: mergeText(b.Text), 82 Text: mergeText(b.Text),
80 Blame: blame(b), 83 Blame: blame(b),
81 Revision: b.Sourcestamp.Revision, 84 Revision: b.Sourcestamp.Revision,
82 } 85 }
83 } 86 }
84 87
85 // getBuilds fetches all of the recent builds from the . Note that 88 // getBuilds fetches all of the recent builds from the . Note that
86 // getBuilds() does not perform ACL checks. 89 // getBuilds() does not perform ACL checks.
87 func getBuilds( 90 func getBuilds(
88 » c context.Context, masterName, builderName string, finished bool, limit int) ( 91 » c context.Context, masterName, builderName string, finished bool, limit int, cursor *datastore.Cursor) (
89 » []*resp.BuildSummary, error) { 92 » []*resp.BuildSummary, *datastore.Cursor, error) {
90 93
91 // TODO(hinoka): Builder specific structs. 94 // TODO(hinoka): Builder specific structs.
92 result := []*resp.BuildSummary{} 95 result := []*resp.BuildSummary{}
93 q := datastore.NewQuery("buildbotBuild") 96 q := datastore.NewQuery("buildbotBuild")
94 q = q.Eq("finished", finished) 97 q = q.Eq("finished", finished)
95 q = q.Eq("master", masterName) 98 q = q.Eq("master", masterName)
96 q = q.Eq("builder", builderName) 99 q = q.Eq("builder", builderName)
97 » if limit != 0 { 100 » q = q.Order("-number")
98 » » q = q.Limit(int32(limit)) 101 » if cursor != nil {
102 » » q = q.Start(*cursor)
99 } 103 }
100 » q = q.Order("-number") 104 » buildbots, nextCursor, err := runBuildsQuery(c, q, int32(limit))
101 » buildbots := []*buildbotBuild{}
102 » err := getBuildQueryBatcher(c).GetAll(c, q, &buildbots)
103 if err != nil { 105 if err != nil {
104 » » return nil, err 106 » » return nil, nil, err
105 } 107 }
106 for _, b := range buildbots { 108 for _, b := range buildbots {
107 result = append(result, getBuildSummary(b)) 109 result = append(result, getBuildSummary(b))
108 } 110 }
109 » return result, nil 111 » return result, nextCursor, nil
112 }
113
114 // maybeSetGetCursor is a cheesy way to implement bidirectional paging with forw ard-only
115 // datastore cursor by creating a mapping of nextCursor -> thisCursor
116 // in memcache. maybeSetGetCursor stores the future mapping, then returns prevC ursor
117 // in the mapping for thisCursor -> prevCursor, if available.
118 func maybeSetGetCursor(c context.Context, thisCursor, nextCursor *datastore.Curs or, limit int) (*datastore.Cursor, bool) {
119 » key := func(c datastore.Cursor) string {
120 » » // Memcache key limit is 250 bytes, hash our cursor to get under this limit.
121 » » blob := sha1.Sum([]byte(c.String()))
122 » » return fmt.Sprintf("v2:cursors:buildbot_builders:%d:%s", limit, base64.StdEncoding.EncodeToString(blob[:]))
123 » }
124 » // Set the next cursor to this cursor mapping, if available.
125 » if nextCursor != nil {
126 » » item := memcache.NewItem(c, key(*nextCursor))
127 » » if thisCursor == nil {
128 » » » // Make sure we know it exists, just empty
129 » » » item.SetValue([]byte{})
130 » » } else {
131 » » » item.SetValue([]byte((*thisCursor).String()))
132 » » }
133 » » item.SetExpiration(24 * time.Hour)
134 » » memcache.Set(c, item)
135 » }
136 » // Try to get the last cursor, if valid and available.
137 » if thisCursor == nil {
138 » » return nil, false
139 » }
140 » if item, err := memcache.GetKey(c, key(*thisCursor)); err == nil {
141 » » if len(item.Value()) == 0 {
142 » » » return nil, true
143 » » }
144 » » if prevCursor, err := datastore.DecodeCursor(c, string(item.Valu e())); err == nil {
145 » » » return &prevCursor, true
146 » » }
147 » }
148 » return nil, false
110 } 149 }
111 150
112 var errMasterNotFound = errors.New( 151 var errMasterNotFound = errors.New(
113 "Either the request resource was not found or you have insufficient perm issions") 152 "Either the request resource was not found or you have insufficient perm issions")
114 var errNotAuth = errors.New("You are not authenticated, try logging in") 153 var errNotAuth = errors.New("You are not authenticated, try logging in")
115 154
116 // builderImpl is the implementation for getting a milo builder page from buildb ot. 155 // builderImpl is the implementation for getting a milo builder page from buildb ot.
117 // This gets: 156 // This gets:
118 // * Current Builds from querying the master json from the datastore. 157 // * Current Builds from querying the master json from the datastore.
119 // * Recent Builds from a cron job that backfills the recent builds. 158 // * Recent Builds from a cron job that backfills the recent builds.
120 func builderImpl(c context.Context, masterName, builderName string, limit int) ( *resp.Builder, error) { 159 func builderImpl(
160 » c context.Context, masterName, builderName string, limit int, cursor str ing) (
161 » *resp.Builder, error) {
162
163 » var thisCursor *datastore.Cursor
164 » if cursor != "" {
165 » » tmpCur, err := datastore.DecodeCursor(c, cursor)
166 » » if err != nil {
167 » » » return nil, fmt.Errorf("bad cursor: %s", err)
168 » » }
169 » » thisCursor = &tmpCur
170 » }
171
121 result := &resp.Builder{ 172 result := &resp.Builder{
122 Name: builderName, 173 Name: builderName,
123 } 174 }
124 master, t, err := getMasterJSON(c, masterName) 175 master, t, err := getMasterJSON(c, masterName)
125 if err != nil { 176 if err != nil {
126 return nil, err 177 return nil, err
127 } 178 }
128 if clock.Now(c).Sub(t) > 2*time.Minute { 179 if clock.Now(c).Sub(t) > 2*time.Minute {
129 warning := fmt.Sprintf( 180 warning := fmt.Sprintf(
130 "WARNING: Master data is stale (last updated %s)", t) 181 "WARNING: Master data is stale (last updated %s)", t)
(...skipping 29 matching lines...) Expand all
160 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour ce.Changes)) 211 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour ce.Changes))
161 for j, cm := range pb.Source.Changes { 212 for j, cm := range pb.Source.Changes {
162 result.PendingBuilds[i].Blame[j] = &resp.Commit{ 213 result.PendingBuilds[i].Blame[j] = &resp.Commit{
163 AuthorEmail: cm.Who, 214 AuthorEmail: cm.Who,
164 CommitURL: cm.Revlink, 215 CommitURL: cm.Revlink,
165 } 216 }
166 } 217 }
167 } 218 }
168 219
169 // This is CPU bound anyways, so there's no need to do this in parallel. 220 // This is CPU bound anyways, so there's no need to do this in parallel.
170 » finishedBuilds, err := getBuilds(c, masterName, builderName, true, limit ) 221 » finishedBuilds, nextCursor, err := getBuilds(c, masterName, builderName, true, limit, thisCursor)
171 if err != nil { 222 if err != nil {
172 return nil, err 223 return nil, err
173 } 224 }
174 » currentBuilds, err := getBuilds(c, masterName, builderName, false, 0) 225 » if prevCursor, ok := maybeSetGetCursor(c, thisCursor, nextCursor, limit) ; ok {
226 » » if prevCursor == nil {
227 » » » // Magic string to signal display prev without cursor
228 » » » result.PrevCursor = "EMPTY"
229 » » } else {
230 » » » result.PrevCursor = (*prevCursor).String()
231 » » }
232 » }
233 » if nextCursor != nil {
234 » » result.NextCursor = (*nextCursor).String()
235 » }
236 » // Cursor is not needed for current builds.
237 » currentBuilds, _, err := getBuilds(c, masterName, builderName, false, 0, nil)
175 if err != nil { 238 if err != nil {
176 return nil, err 239 return nil, err
177 } 240 }
178 // currentBuilds is presented in reversed order, so flip it 241 // currentBuilds is presented in reversed order, so flip it
179 for i, j := 0, len(currentBuilds)-1; i < j; i, j = i+1, j-1 { 242 for i, j := 0, len(currentBuilds)-1; i < j; i, j = i+1, j-1 {
180 currentBuilds[i], currentBuilds[j] = currentBuilds[j], currentBu ilds[i] 243 currentBuilds[i], currentBuilds[j] = currentBuilds[j], currentBu ilds[i]
181 } 244 }
182 result.CurrentBuilds = currentBuilds 245 result.CurrentBuilds = currentBuilds
183 246
184 for _, fb := range finishedBuilds { 247 for _, fb := range finishedBuilds {
185 if fb != nil { 248 if fb != nil {
186 result.FinishedBuilds = append(result.FinishedBuilds, fb ) 249 result.FinishedBuilds = append(result.FinishedBuilds, fb )
187 } 250 }
188 } 251 }
189 return result, nil 252 return result, nil
190 } 253 }
OLDNEW
« no previous file with comments | « milo/api/resp/builder.go ('k') | milo/appengine/buildbot/console.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698