@@ -16,10 +16,12 @@ import (
16
16
"log"
17
17
"net/http"
18
18
"sort"
19
+ "strconv"
19
20
"strings"
20
21
"time"
21
22
22
23
"cloud.google.com/go/datastore"
24
+ "golang.org/x/build/cmd/coordinator/internal/lucipoll"
23
25
"golang.org/x/build/dashboard"
24
26
"golang.org/x/build/internal/releasetargets"
25
27
"golang.org/x/build/maintner/maintnerd/apipb"
@@ -44,39 +46,55 @@ type MaintnerClient interface {
44
46
GetDashboard (ctx context.Context , in * apipb.DashboardRequest , opts ... grpc.CallOption ) (* apipb.DashboardResponse , error )
45
47
}
46
48
49
+ type luciClient interface {
50
+ PostSubmitSnapshot () lucipoll.Snapshot
51
+ }
52
+
47
53
type Handler struct {
48
54
// Datastore is a client used for fetching build status. If nil, it uses in-memory storage of build status.
49
55
Datastore * datastore.Client
50
56
// Maintner is a client for Maintner, used for fetching lists of commits.
51
57
Maintner MaintnerClient
58
+ // LUCI is a client for LUCI, used for fetching build results from there.
59
+ LUCI luciClient
52
60
53
61
// memoryResults is an in-memory storage of CI results. Used in development and testing for datastore data.
54
62
memoryResults map [string ][]string
55
63
}
56
64
57
- func (d * Handler ) ServeHTTP (rw http.ResponseWriter , r * http.Request ) {
65
+ func (d * Handler ) ServeHTTP (w http.ResponseWriter , req * http.Request ) {
66
+ showLUCI , _ := strconv .ParseBool (req .URL .Query ().Get ("showluci" ))
67
+ if legacyOnly , _ := strconv .ParseBool (req .URL .Query ().Get ("legacyonly" )); legacyOnly {
68
+ showLUCI = false
69
+ }
70
+
71
+ var luci lucipoll.Snapshot
72
+ if d .LUCI != nil && showLUCI {
73
+ luci = d .LUCI .PostSubmitSnapshot ()
74
+ }
75
+
58
76
dd := & data {
59
- Builders : d .getBuilders (dashboard .Builders ),
60
- Commits : d .commits (r .Context ()),
77
+ Builders : d .getBuilders (dashboard .Builders , luci ),
78
+ Commits : d .commits (req .Context (), luci ),
61
79
Package : dashPackage {Name : "Go" },
62
80
}
63
81
64
82
var buf bytes.Buffer
65
83
if err := templ .Execute (& buf , dd ); err != nil {
66
84
log .Printf ("handleDashboard: error rendering template: %v" , err )
67
- http .Error (rw , http .StatusText (http .StatusInternalServerError ), http .StatusInternalServerError )
85
+ http .Error (w , http .StatusText (http .StatusInternalServerError ), http .StatusInternalServerError )
68
86
return
69
87
}
70
- buf .WriteTo (rw )
88
+ buf .WriteTo (w )
71
89
}
72
90
73
- func (d * Handler ) commits (ctx context.Context ) []* commit {
74
- var commits []* commit
91
+ func (d * Handler ) commits (ctx context.Context , luci lucipoll.Snapshot ) []* commit {
75
92
resp , err := d .Maintner .GetDashboard (ctx , & apipb.DashboardRequest {})
76
93
if err != nil {
77
94
log .Printf ("handleDashboard: error fetching from maintner: %v" , err )
78
- return commits
95
+ return nil
79
96
}
97
+ var commits []* commit
80
98
for _ , c := range resp .GetCommits () {
81
99
commits = append (commits , & commit {
82
100
Desc : c .Title ,
@@ -85,24 +103,26 @@ func (d *Handler) commits(ctx context.Context) []*commit {
85
103
User : formatGitAuthor (c .AuthorName , c .AuthorEmail ),
86
104
})
87
105
}
88
- d .getResults (ctx , commits )
106
+ d .getResults (ctx , commits , luci )
89
107
return commits
90
108
}
91
109
92
- // getResults populates result data on commits, fetched from Datastore or in-memory storage.
93
- func (d * Handler ) getResults (ctx context.Context , commits []* commit ) {
94
- if d .Datastore == nil {
110
+ // getResults populates result data on commits, fetched from Datastore or in-memory storage
111
+ // and, if luci is non-zero, also from LUCI.
112
+ func (d * Handler ) getResults (ctx context.Context , commits []* commit , luci lucipoll.Snapshot ) {
113
+ if d .Datastore != nil {
114
+ getDatastoreResults (ctx , d .Datastore , commits , "go" )
115
+ } else {
95
116
for _ , c := range commits {
96
117
if result , ok := d .memoryResults [c .Hash ]; ok {
97
118
c .ResultData = result
98
119
}
99
120
}
100
- return
101
121
}
102
- getDatastoreResults ( ctx , d . Datastore , commits , "go" )
122
+ appendLUCIResults ( luci , commits , "go" )
103
123
}
104
124
105
- func (d * Handler ) getBuilders (conf map [string ]* dashboard.BuildConfig ) []* builder {
125
+ func (d * Handler ) getBuilders (conf map [string ]* dashboard.BuildConfig , luci lucipoll. Snapshot ) []* builder {
106
126
bm := make (map [string ]builder )
107
127
for _ , b := range conf {
108
128
if ! b .BuildsRepoPostSubmit ("go" , "master" , "master" ) {
@@ -111,12 +131,36 @@ func (d *Handler) getBuilders(conf map[string]*dashboard.BuildConfig) []*builder
111
131
db := bm [b .GOOS ()]
112
132
db .OS = b .GOOS ()
113
133
db .Archs = append (db .Archs , & arch {
114
- Arch : b .GOARCH (),
134
+ os : b . GOOS (), Arch : b .GOARCH (),
115
135
Name : b .Name ,
116
- Tag : strings .TrimPrefix (b .Name , fmt .Sprintf ("%s-%s-" , b .GOOS (), b .GOARCH ())),
136
+ // Tag is the part after "os-arch", if any, without leading dash.
137
+ Tag : strings .TrimPrefix (strings .TrimPrefix (b .Name , fmt .Sprintf ("%s-%s" , b .GOOS (), b .GOARCH ())), "-" ),
117
138
})
118
139
bm [b .GOOS ()] = db
119
140
}
141
+
142
+ for _ , b := range luci .Builders {
143
+ if b .Repo != "go" || b .GoBranch != "master" {
144
+ continue
145
+ }
146
+ db := bm [b .Target .GOOS ]
147
+ db .OS = b .Target .GOOS
148
+ tagFriendly := b .Name + "-🐇"
149
+ if after , ok := strings .CutPrefix (tagFriendly , fmt .Sprintf ("gotip-%s-%s_" , b .Target .GOOS , b .Target .GOARCH )); ok {
150
+ // Convert os-arch_osversion-mod1-mod2 (an underscore at start of "_osversion")
151
+ // to have os-arch-osversion-mod1-mod2 (a dash at start of "-osversion") form.
152
+ // The tag computation below uses this to find both "osversion-mod1" or "mod1".
153
+ tagFriendly = fmt .Sprintf ("gotip-%s-%s-" , b .Target .GOOS , b .Target .GOARCH ) + after
154
+ }
155
+ db .Archs = append (db .Archs , & arch {
156
+ os : b .Target .GOOS , Arch : b .Target .GOARCH ,
157
+ Name : b .Name ,
158
+ // Tag is the part after "os-arch", if any, without leading dash.
159
+ Tag : strings .TrimPrefix (strings .TrimPrefix (tagFriendly , fmt .Sprintf ("gotip-%s-%s" , b .Target .GOOS , b .Target .GOARCH )), "-" ),
160
+ })
161
+ bm [b .Target .GOOS ] = db
162
+ }
163
+
120
164
var builders builderSlice
121
165
for _ , db := range bm {
122
166
db := db
@@ -128,18 +172,12 @@ func (d *Handler) getBuilders(conf map[string]*dashboard.BuildConfig) []*builder
128
172
}
129
173
130
174
type arch struct {
131
- Arch string
132
- Name string
133
- Tag string
175
+ os , Arch string
176
+ Name string
177
+ Tag string
134
178
}
135
179
136
- func (a arch ) FirstClass () bool {
137
- segs := strings .SplitN (a .Name , "-" , 3 )
138
- if len (segs ) < 2 {
139
- return false
140
- }
141
- return releasetargets .IsFirstClass (segs [0 ], segs [1 ])
142
- }
180
+ func (a arch ) FirstClass () bool { return releasetargets .IsFirstClass (a .os , a .Arch ) }
143
181
144
182
type archSlice []* arch
145
183
@@ -217,8 +255,13 @@ type dashPackage struct {
217
255
}
218
256
219
257
type commit struct {
220
- Desc string
221
- Hash string
258
+ Desc string
259
+ Hash string
260
+ // ResultData is a copy of the [Commit.ResultData] field from datastore,
261
+ // with an additional rule that the second '|'-separated value may be "infra_failure"
262
+ // to indicate a problem with the infrastructure rather than the code being tested.
263
+ //
264
+ // It can also have the form of "builder|BuildingURL" for in progress builds.
222
265
ResultData []string
223
266
Time string
224
267
User string
@@ -236,28 +279,43 @@ func (c *commit) ShortUser() string {
236
279
return user
237
280
}
238
281
239
- func (c * commit ) ResultForBuilder (builder string ) result {
282
+ func (c * commit ) ResultForBuilder (builder string ) * result {
240
283
for _ , rd := range c .ResultData {
241
284
segs := strings .Split (rd , "|" )
285
+ if len (segs ) == 2 && segs [0 ] == builder {
286
+ return & result {
287
+ BuildingURL : segs [1 ],
288
+ }
289
+ }
242
290
if len (segs ) < 4 {
243
291
continue
244
292
}
245
293
if segs [0 ] == builder {
246
- return result {
294
+ return & result {
247
295
OK : segs [1 ] == "true" ,
296
+ Noise : segs [1 ] == "infra_failure" ,
248
297
LogHash : segs [2 ],
249
298
}
250
299
}
251
300
}
252
- return result {}
301
+ return nil
253
302
}
254
303
255
304
type result struct {
256
305
BuildingURL string
257
306
OK bool
307
+ Noise bool
258
308
LogHash string
259
309
}
260
310
311
+ func (r result ) LogURL () string {
312
+ if strings .HasPrefix (r .LogHash , "https://" ) {
313
+ return r .LogHash
314
+ } else {
315
+ return "https://build.golang.org/log/" + r .LogHash
316
+ }
317
+ }
318
+
261
319
// formatGitAuthor formats the git author name and email (as split by
262
320
// maintner) back into the unified string how they're stored in a git
263
321
// commit, so the shortUser func (used by the HTML template) can parse
0 commit comments