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

Side by Side Diff: appengine/cmd/milo/buildbucket/builder.go

Issue 2134673003: milo: buildbucket builer view (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-go@master
Patch Set: apparently our bots cannot process a patch for a file that has spaces :( Created 4 years, 5 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
OLDNEW
(Empty)
1 // Copyright 2016 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 package buildbucket
6
7 import (
8 "encoding/json"
9 "fmt"
10 "net/url"
11 "os"
12 "path"
13 "path/filepath"
14 "strconv"
15 "strings"
16 "time"
17
18 "golang.org/x/net/context"
19 "google.golang.org/api/googleapi"
20
21 "github.com/luci/luci-go/appengine/cmd/milo/resp"
22 "github.com/luci/luci-go/common/api/buildbucket/buildbucket/v1"
23 "github.com/luci/luci-go/common/clock"
24 "github.com/luci/luci-go/common/errors"
25 "github.com/luci/luci-go/common/logging"
hinoka 2016/07/08 23:26:05 Rest of milo uses. log "github.com/luci/..../logg
nodir 2016/07/09 00:00:49 Done.
26 "github.com/luci/luci-go/common/parallel"
27 "github.com/luci/luci-go/common/retry"
28 )
29
30 // search executes the search request with retries and exponential back-off.
31 func search(c context.Context, client *buildbucket.Service, req *buildbucket.Sea rchCall) (
32 *buildbucket.ApiSearchResponseMessage, error) {
33
34 var res *buildbucket.ApiSearchResponseMessage
35 err := retry.Retry(
36 c,
37 retry.TransientOnly(retry.Default),
38 func() error {
39 var err error
40 res, err = req.Do()
41 if apiErr, ok := err.(*googleapi.Error); ok && apiErr.Co de >= 500 {
42 err = errors.WrapTransient(apiErr)
43 }
44 return err
45 },
46 func(err error, wait time.Duration) {
47 logging.WithError(err).Warningf(c, "buildbucket search r equest failed transiently, will retry in %s", wait)
48 })
49 return res, err
50 }
51
52 // fetchBuilds fetches builds given a criteria.
53 // The returned builds are sorted by build creation descending.
54 // count defines maximum number of builds to fetch, defaults to 100.
55 func fetchBuilds(c context.Context, client *buildbucket.Service, bucket, builder ,
56 status string, count int) ([]*buildbucket.ApiBuildMessage, error) {
57
58 req := client.Search()
59 req.Bucket(bucket)
60 req.Status(status)
61 req.Tag("builder:" + builder)
62
63 if count <= 0 {
64 count = 100
65 }
66
67 fetched := make([]*buildbucket.ApiBuildMessage, 0, count)
68 start := clock.Now(c)
69 for len(fetched) < count {
70 req.MaxBuilds(int64(count - len(fetched)))
71
72 res, err := search(c, client, req)
73 switch {
74 case err != nil:
75 return fetched, err
76 case res.Error != nil:
77 return fetched, fmt.Errorf(res.Error.Message)
78 }
79
80 fetched = append(fetched, res.Builds...)
81
82 if len(res.Builds) == 0 || res.NextCursor == "" {
83 break
84 }
85 req.StartCursor(res.NextCursor)
86 }
87 logging.Debugf(c, "Fetched %d %s builds in %s", len(fetched), status, cl ock.Since(c, start))
88 return fetched, nil
89 }
90
91 // toMiloBuild converts a buildbucket build to a milo build.
92 // The probability of returning an error if very low.
93 func toMiloBuild(c context.Context, build *buildbucket.ApiBuildMessage) (*resp.B uildSummary, error) {
94 // Parsing of parameters and result details is best effort.
95 var params buildParameters
96 if err := json.NewDecoder(strings.NewReader(build.ParametersJson)).Decod e(&params); err != nil {
97 logging.Errorf(c, "Could not parse parameters of build %d: %s", build.Id, err)
98 }
99 var resultDetails resultDetails
100 if err := json.NewDecoder(strings.NewReader(build.ResultDetailsJson)).De code(&resultDetails); err != nil {
101 logging.Errorf(c, "Could not parse result details of build %d: % s", build.Id, err)
102 }
103
104 result := &resp.BuildSummary{
105 Text: []string{fmt.Sprintf("buildbucket id %d", build.Id)},
106 Revision: resultDetails.Properties.GotRevision,
107 }
108 if result.Revision == "" {
109 result.Revision = params.Properties.Revision
110 }
111
112 var err error
113 result.Status, err = parseStatus(build)
114 if err != nil {
115 // almost never happens
116 return nil, err
117 }
118
119 result.PendingTime.Started = parseTimestamp(build.CreatedTs)
120 switch build.Status {
121 case "SCHEDULED":
122 result.PendingTime.Duration = clock.Since(c, result.PendingTime. Started)
123
124 case "STARTED":
hinoka 2016/07/08 23:26:05 You can set pending interval here too (might be us
nodir 2016/07/09 00:00:49 good point, done
125 result.ExecutionTime.Started = parseTimestamp(build.StatusChange dTs)
126 result.ExecutionTime.Duration = clock.Since(c, result.PendingTim e.Started)
127
128 case "COMPLETED":
hinoka 2016/07/08 23:26:05 No started time?
nodir 2016/07/09 00:00:49 unfortunately buildbucket does not provide it; upd
129 // we don't have execution duration.
130 result.ExecutionTime.Finished = parseTimestamp(build.CompletedTs )
131 }
132
133 cl := getChangeList(build, &params, &resultDetails)
134 if cl != nil {
135 result.Blame = []*resp.Commit{cl}
136 }
137
138 tags := ParseTags(build.Tags)
139
140 if build.Url != "" {
141 u := build.Url
142 parsed, err := url.Parse(u)
143
144 // map milo links to itself
145 switch {
146 case err != nil:
147 logging.Errorf(c, "invalid URL in build %d: %s", build.I d, err)
148 case parsed.Host == "luci-milo.appspot.com":
149 parsed.Host = ""
150 parsed.Scheme = ""
151 u = parsed.String()
152 }
153
154 result.Link = &resp.Link{
155 URL: u,
156 Label: strconv.FormatInt(build.Id, 10),
157 }
158
159 // compute the best link label
160 if taskID := tags["swarming_task_id"]; taskID != "" {
161 result.Link.Label = taskID
162 } else if resultDetails.Properties.BuildNumber != 0 {
163 result.Link.Label = strconv.Itoa(resultDetails.Propertie s.BuildNumber)
164 } else if parsed != nil {
165 // does the URL look like a buildbot build URL?
166 pattern := fmt.Sprintf(
167 `/%s/builders/%s/builds/`,
168 strings.TrimPrefix(build.Bucket, "master."), par ams.BuilderName)
169 beforeBuildNumber, buildNumberStr := path.Split(parsed.P ath)
170 _, err := strconv.Atoi(buildNumberStr)
171 if strings.HasSuffix(beforeBuildNumber, pattern) && err == nil {
172 result.Link.Label = buildNumberStr
173 }
174 }
175 }
176
177 return result, nil
178 }
179
180 func getDebugBuilds(c context.Context, bucket, builder string, maxCompletedBuild s int, target *resp.Builder) error {
181 resFile, err := os.Open(filepath.Join("testdata", "buildbucket", bucket, builder+".json"))
182 if err != nil {
183 return err
184 }
185 defer resFile.Close()
186
187 res := &buildbucket.ApiSearchResponseMessage{}
188 if err := json.NewDecoder(resFile).Decode(res); err != nil {
189 return err
190 }
191
192 for _, bb := range res.Builds {
193 mb, err := toMiloBuild(c, bb)
194 if err != nil {
195 // this is debugging, so it is fine to stop on first err or.
196 return err
197 }
198 switch mb.Status {
199 case resp.NotRun:
200 target.PendingBuilds = append(target.PendingBuilds, mb)
201
202 case resp.Running:
203 target.CurrentBuilds = append(target.CurrentBuilds, mb)
204
205 case resp.Success, resp.Failure, resp.InfraFailure, resp.Warning :
206 if len(target.FinishedBuilds) < maxCompletedBuilds {
207 target.FinishedBuilds = append(target.FinishedBu ilds, mb)
208 }
209
210 default:
211 panic("impossible")
212 }
213 }
214 return nil
215 }
216
217 // builderImpl is the implementation for getting a milo builder page from buildb ucket.
218 func builderImpl(c context.Context, server, bucket, builder string, maxCompleted Builds int) (*resp.Builder, error) {
219 if maxCompletedBuilds <= 0 {
220 maxCompletedBuilds = 20
221 }
222
223 result := &resp.Builder{
224 Name: builder,
225 }
226 if server == "debug" {
227 if err := getDebugBuilds(c, bucket, builder, maxCompletedBuilds, result); err != nil {
hinoka 2016/07/08 23:26:05 err := getDebugBuilds(...) return result, err And
nodir 2016/07/09 00:00:49 Done.
228 return result, err
229 }
230 } else {
231 client, err := newClient(c, server)
232 if err != nil {
233 return nil, err
234 }
235
236 fetch := func(target *[]*resp.BuildSummary, status string, count int) error {
237 builds, err := fetchBuilds(c, client, bucket, builder, s tatus, count)
238 if err != nil {
239 logging.Errorf(c, "Could not fetch builds with s tatus %s: %s", status, err)
240 return err
241 }
242 *target = make([]*resp.BuildSummary, 0, len(builds))
243 for _, bb := range builds {
244 mb, err := toMiloBuild(c, bb)
245 if err != nil {
hinoka 2016/07/08 23:26:05 Suggestion: Add a placeholder build, with status i
nodir 2016/07/09 00:00:49 good idea, done
246 logging.Errorf(c, "Invalid build %d: %s" , bb.Id, err)
247 // Do not bail out because of one bad bu ild.
248 // Also toMiloBuild almost never returns an error.
249 continue
250 }
251 *target = append(*target, mb)
252 }
253 return nil
254 }
255 // fetch pending, current and finished builds concurrently.
256 // Why not a single request? Because we need different build num ber
257 // limits for different statuses.
258 err = parallel.FanOutIn(func(work chan<- func() error) {
259 work <- func() error {
260 return fetch(&result.PendingBuilds, StatusSchedu led, 0)
261 }
262 work <- func() error {
263 return fetch(&result.CurrentBuilds, StatusStarte d, 0)
264 }
265 work <- func() error {
266 return fetch(&result.FinishedBuilds, StatusCompl eted, maxCompletedBuilds)
267 }
268 })
269 if err != nil {
270 return result, err
271 }
272 }
273 return result, nil
274 }
275
276 // parseTimestamp converts buildbucket timestamp in microseconds to time.Time
277 func parseTimestamp(microseconds int64) time.Time {
278 if microseconds == 0 {
279 return time.Time{}
280 }
281 return time.Unix(microseconds/1e6, microseconds%1e6*1000).UTC()
282 }
283
284 type newBuildsFirst []*resp.BuildSummary
285
286 func (a newBuildsFirst) Len() int { return len(a) }
287 func (a newBuildsFirst) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
288 func (a newBuildsFirst) Less(i, j int) bool {
289 return a[i].PendingTime.Started.After(a[j].PendingTime.Started)
290 }
OLDNEW
« no previous file with comments | « no previous file | appengine/cmd/milo/buildbucket/builder_test.go » ('j') | appengine/cmd/milo/buildbucket/common.go » ('J')

Powered by Google App Engine
This is Rietveld 408576698