Skip to content

Commit 15521bc

Browse files
committed
cmd/coordinator: clean up reverse buildlet code, export status JSON
Will be used for dynamic creation/destruction of Mac VMs in subsequent CL. Updates golang/go#9495 (Mac virtualization) Updates golang/go#15760 (monitoring) Change-Id: I48b17589b258d5d742bad5a3ddae18de98778149 Reviewed-on: https://go-review.googlesource.com/37457 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 79b3e11 commit 15521bc

File tree

4 files changed

+184
-52
lines changed

4 files changed

+184
-52
lines changed

cmd/coordinator/coordinator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ func main() {
283283
http.HandleFunc("/reverse", handleReverse)
284284
http.HandleFunc("/style.css", handleStyleCSS)
285285
http.HandleFunc("/try", handleTryStatus)
286+
http.HandleFunc("/status/reverse.json", reversePool.ServeReverseStatusJSON)
286287
http.Handle("/buildlet/create", requireBuildletProxyAuth(http.HandlerFunc(handleBuildletCreate)))
287288
http.Handle("/buildlet/list", requireBuildletProxyAuth(http.HandlerFunc(handleBuildletList)))
288289
go func() {

cmd/coordinator/reverse.go

Lines changed: 121 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ work, go to:
3030
import (
3131
"bytes"
3232
"context"
33+
"encoding/json"
3334
"errors"
3435
"fmt"
3536
"io"
@@ -45,50 +46,110 @@ import (
4546
"golang.org/x/build/buildlet"
4647
"golang.org/x/build/dashboard"
4748
"golang.org/x/build/revdial"
49+
"golang.org/x/build/types"
4850
)
4951

5052
const minBuildletVersion = 1
5153

52-
var reversePool = &reverseBuildletPool{
53-
available: make(chan token, 1),
54-
}
54+
var reversePool = new(reverseBuildletPool)
5555

5656
type token struct{}
5757

5858
type reverseBuildletPool struct {
59-
available chan token // best-effort tickle when any buildlet becomes free
60-
61-
mu sync.Mutex // guards buildlets and their fields
59+
mu sync.Mutex // guards all fields, including fields of *reverseBuildlet
60+
// TODO: switch to a map[hostType][]buildlets or map of set.
6261
buildlets []*reverseBuildlet
62+
wakeChan map[string]chan token // hostType => best-effort wake-up chan when buildlet free
63+
waiters map[string]int // hostType => number waiters blocked in GetBuildlet
64+
}
65+
66+
func (p *reverseBuildletPool) ServeReverseStatusJSON(w http.ResponseWriter, r *http.Request) {
67+
w.Header().Set("Content-Type", "application/json")
68+
status := p.buildReverseStatusJSON()
69+
j, _ := json.MarshalIndent(status, "", "\t")
70+
w.Write(j)
6371
}
6472

65-
var errInUse = errors.New("all buildlets are in use")
73+
func (p *reverseBuildletPool) buildReverseStatusJSON() *types.ReverseBuilderStatus {
74+
status := &types.ReverseBuilderStatus{}
6675

67-
func (p *reverseBuildletPool) tryToGrab(hostType string) (*buildlet.Client, error) {
6876
p.mu.Lock()
6977
defer p.mu.Unlock()
70-
candidates := 0
7178
for _, b := range p.buildlets {
72-
isCandidate := b.hostType == hostType
73-
if isCandidate {
74-
candidates++
79+
hs := status.Host(b.hostType)
80+
if hs.Machines == nil {
81+
hs.Machines = make(map[string]*types.ReverseBuilder)
82+
}
83+
hs.Connected++
84+
bs := &types.ReverseBuilder{
85+
Name: b.hostname,
86+
HostType: b.hostType,
87+
ConnectedSec: time.Since(b.regTime).Seconds(),
88+
Version: b.version,
89+
}
90+
if b.inUse && !b.inHealthCheck {
91+
hs.Busy++
92+
bs.Busy = true
93+
bs.BusySec = time.Since(b.inUseTime).Seconds()
94+
} else {
95+
hs.Idle++
96+
bs.IdleSec = time.Since(b.inUseTime).Seconds()
7597
}
76-
if isCandidate && !b.inUse {
77-
// Found an unused match.
78-
b.inUse = true
79-
b.inUseTime = time.Now()
80-
return b.client, nil
98+
99+
hs.Machines[b.hostname] = bs
100+
}
101+
for hostType, waiters := range p.waiters {
102+
status.Host(hostType).Waiters = waiters
103+
}
104+
for hostType, hc := range dashboard.Hosts {
105+
if hc.ExpectNum > 0 {
106+
status.Host(hostType).Expect = hc.ExpectNum
81107
}
82108
}
83-
if candidates == 0 {
84-
return nil, fmt.Errorf("no buildlets registered for host type %q", hostType)
109+
return status
110+
}
111+
112+
// tryToGrab returns non-nil bc on success if a buildlet is free.
113+
//
114+
// Otherwise it returns how many were busy, which might be 0 if none
115+
// were (yet?) registered. The busy valid is only valid if bc == nil.
116+
func (p *reverseBuildletPool) tryToGrab(hostType string) (bc *buildlet.Client, busy int) {
117+
p.mu.Lock()
118+
defer p.mu.Unlock()
119+
for _, b := range p.buildlets {
120+
if b.hostType != hostType {
121+
continue
122+
}
123+
if b.inUse {
124+
busy++
125+
continue
126+
}
127+
// Found an unused match.
128+
b.inUse = true
129+
b.inUseTime = time.Now()
130+
return b.client, 0
131+
}
132+
return nil, busy
133+
}
134+
135+
func (p *reverseBuildletPool) getWakeChan(hostType string) chan token {
136+
p.mu.Lock()
137+
defer p.mu.Unlock()
138+
if p.wakeChan == nil {
139+
p.wakeChan = make(map[string]chan token)
140+
}
141+
c, ok := p.wakeChan[hostType]
142+
if !ok {
143+
c = make(chan token)
144+
p.wakeChan[hostType] = c
85145
}
86-
return nil, errInUse
146+
return c
87147
}
88148

89-
func (p *reverseBuildletPool) noteBuildletAvailable() {
149+
func (p *reverseBuildletPool) noteBuildletAvailable(hostType string) {
150+
wake := p.getWakeChan(hostType)
90151
select {
91-
case p.available <- token{}:
152+
case wake <- token{}:
92153
default:
93154
}
94155
}
@@ -168,7 +229,7 @@ func (p *reverseBuildletPool) healthCheckBuildlet(b *reverseBuildlet) bool {
168229
b.inUse = false
169230
b.inHealthCheck = false
170231
b.inUseTime = time.Now()
171-
p.noteBuildletAvailable()
232+
go p.noteBuildletAvailable(b.hostType)
172233
return true
173234
}
174235

@@ -188,45 +249,53 @@ func highPriChan(hostType string) chan *buildlet.Client {
188249
return c
189250
}
190251

252+
func (p *reverseBuildletPool) updateWaiterCounter(hostType string, delta int) {
253+
p.mu.Lock()
254+
defer p.mu.Unlock()
255+
if p.waiters == nil {
256+
p.waiters = make(map[string]int)
257+
}
258+
p.waiters[hostType] += delta
259+
}
260+
191261
func (p *reverseBuildletPool) GetBuildlet(ctx context.Context, hostType string, lg logger) (*buildlet.Client, error) {
262+
p.updateWaiterCounter(hostType, 1)
263+
defer p.updateWaiterCounter(hostType, -1)
192264
seenErrInUse := false
193265
isHighPriority, _ := ctx.Value(highPriorityOpt{}).(bool)
194266
sp := lg.createSpan("wait_static_builder", hostType)
195267
for {
196-
b, err := p.tryToGrab(hostType)
197-
if err == errInUse {
198-
if !seenErrInUse {
199-
lg.logEventTime("waiting_machine_in_use")
200-
seenErrInUse = true
201-
}
202-
var highPri chan *buildlet.Client
203-
if isHighPriority {
204-
highPri = highPriChan(hostType)
205-
}
268+
bc, busy := p.tryToGrab(hostType)
269+
if bc != nil {
206270
select {
207-
case <-ctx.Done():
208-
return nil, sp.done(ctx.Err())
209-
case bc := <-highPri:
271+
case highPriChan(hostType) <- bc:
272+
// Somebody else was more important.
273+
default:
210274
sp.done(nil)
211275
return p.cleanedBuildlet(bc, lg)
276+
}
277+
}
278+
if busy > 0 && !seenErrInUse {
279+
lg.logEventTime("waiting_machine_in_use")
280+
seenErrInUse = true
281+
}
282+
var highPri chan *buildlet.Client
283+
if isHighPriority {
284+
highPri = highPriChan(hostType)
285+
}
286+
select {
287+
case <-ctx.Done():
288+
return nil, sp.done(ctx.Err())
289+
case bc := <-highPri:
290+
sp.done(nil)
291+
return p.cleanedBuildlet(bc, lg)
292+
293+
case <-time.After(10 * time.Second):
212294
// As multiple goroutines can be listening for
213295
// the available signal, it must be treated as
214296
// a best effort signal. So periodically try
215-
// to grab a buildlet again:
216-
case <-time.After(10 * time.Second):
217-
case <-p.available:
218-
}
219-
} else if err != nil {
220-
sp.done(err)
221-
return nil, err
222-
} else {
223-
select {
224-
case highPriChan(hostType) <- b:
225-
// Somebody else was more important.
226-
default:
227-
sp.done(nil)
228-
return p.cleanedBuildlet(b, lg)
229-
}
297+
// to grab a buildlet again.
298+
case <-p.getWakeChan(hostType):
230299
}
231300
}
232301
}
@@ -329,7 +398,7 @@ func (p *reverseBuildletPool) CanBuild(hostType string) bool {
329398

330399
func (p *reverseBuildletPool) addBuildlet(b *reverseBuildlet) {
331400
p.mu.Lock()
332-
defer p.noteBuildletAvailable()
401+
defer p.noteBuildletAvailable(b.hostType)
333402
defer p.mu.Unlock()
334403
p.buildlets = append(p.buildlets, b)
335404
go p.healthCheckBuildletLoop(b)

dashboard/builders.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ var Hosts = map[string]*HostConfig{
6161
},
6262
"host-linux-arm": &HostConfig{
6363
IsReverse: true,
64+
ExpectNum: 50,
6465
env: []string{"GOROOT_BOOTSTRAP=/usr/local/go"},
6566
ReverseAliases: []string{"linux-arm", "linux-arm-arm5"},
6667
},
@@ -139,6 +140,7 @@ var Hosts = map[string]*HostConfig{
139140
},
140141
"host-darwin-10_8": &HostConfig{
141142
IsReverse: true,
143+
ExpectNum: 1,
142144
Notes: "MacStadium OS X 10.8 VM under VMWare ESXi",
143145
env: []string{
144146
"GOROOT_BOOTSTRAP=/Users/gopher/go1.4",
@@ -147,6 +149,7 @@ var Hosts = map[string]*HostConfig{
147149
},
148150
"host-darwin-10_10": &HostConfig{
149151
IsReverse: true,
152+
ExpectNum: 2,
150153
Notes: "MacStadium OS X 10.10 VM under VMWare ESXi",
151154
env: []string{
152155
"GOROOT_BOOTSTRAP=/Users/gopher/go1.4",
@@ -155,6 +158,7 @@ var Hosts = map[string]*HostConfig{
155158
},
156159
"host-darwin-10_11": &HostConfig{
157160
IsReverse: true,
161+
ExpectNum: 15,
158162
Notes: "MacStadium OS X 10.11 VM under VMWare ESXi",
159163
env: []string{
160164
"GOROOT_BOOTSTRAP=/Users/gopher/go1.4",
@@ -163,6 +167,7 @@ var Hosts = map[string]*HostConfig{
163167
},
164168
"host-darwin-10_12": &HostConfig{
165169
IsReverse: true,
170+
ExpectNum: 2,
166171
Notes: "MacStadium OS X 10.12 VM under VMWare ESXi",
167172
env: []string{
168173
"GOROOT_BOOTSTRAP=/Users/gopher/go1.4",
@@ -178,30 +183,35 @@ var Hosts = map[string]*HostConfig{
178183
"host-linux-ppc64-osu": &HostConfig{
179184
Notes: "Debian jessie; run by Go team on osuosl.org",
180185
IsReverse: true,
186+
ExpectNum: 5,
181187
env: []string{"GOROOT_BOOTSTRAP=/usr/local/go-bootstrap"},
182188
ReverseAliases: []string{"linux-ppc64-buildlet"},
183189
},
184190
"host-linux-ppc64le-osu": &HostConfig{
185191
Notes: "Debian jessie; run by Go team on osuosl.org",
186192
IsReverse: true,
193+
ExpectNum: 5,
187194
env: []string{"GOROOT_BOOTSTRAP=/usr/local/go-bootstrap"},
188195
ReverseAliases: []string{"linux-ppc64le-buildlet"},
189196
},
190197
"host-linux-arm64-linaro": &HostConfig{
191198
Notes: "Ubuntu wily; run by Go team, from linaro",
192199
IsReverse: true,
200+
ExpectNum: 5,
193201
env: []string{"GOROOT_BOOTSTRAP=/usr/local/go-bootstrap"},
194202
ReverseAliases: []string{"linux-arm64-buildlet"},
195203
},
196204
"host-solaris-amd64": &HostConfig{
197205
Notes: "run by Go team on Joyent, on a SmartOS 'infrastructure container'",
198206
IsReverse: true,
207+
ExpectNum: 5,
199208
env: []string{"GOROOT_BOOTSTRAP=/root/go-solaris-amd64-bootstrap"},
200209
ReverseAliases: []string{"solaris-amd64-smartosbuildlet"},
201210
},
202211
"host-linux-mips": &HostConfig{
203212
Notes: "Run by Brendan Kirby, imgtec.com",
204213
IsReverse: true,
214+
ExpectNum: 1,
205215
env: []string{
206216
"GOROOT_BOOTSTRAP=/usr/local/go-bootstrap-mips",
207217
"GOARCH=mips",
@@ -213,6 +223,7 @@ var Hosts = map[string]*HostConfig{
213223
"host-linux-mipsle": &HostConfig{
214224
Notes: "Run by Brendan Kirby, imgtec.com",
215225
IsReverse: true,
226+
ExpectNum: 1,
216227
env: []string{
217228
"GOROOT_BOOTSTRAP=/usr/local/go-bootstrap-mipsle",
218229
"GOARCH=mipsle",
@@ -223,6 +234,7 @@ var Hosts = map[string]*HostConfig{
223234
"host-linux-mips64": &HostConfig{
224235
Notes: "Run by Brendan Kirby, imgtec.com",
225236
IsReverse: true,
237+
ExpectNum: 1,
226238
env: []string{
227239
"GOROOT_BOOTSTRAP=/usr/local/go-bootstrap-mips64",
228240
"GOARCH=mips64",
@@ -234,6 +246,7 @@ var Hosts = map[string]*HostConfig{
234246
"host-linux-mips64le": &HostConfig{
235247
Notes: "Run by Brendan Kirby, imgtec.com",
236248
IsReverse: true,
249+
ExpectNum: 1,
237250
env: []string{
238251
"GOROOT_BOOTSTRAP=/usr/local/go-bootstrap-mips64le",
239252
"GOARCH=mips64le",
@@ -299,6 +312,9 @@ type HostConfig struct {
299312
machineType string // optional GCE instance type
300313
RegularDisk bool // if true, use spinning disk instead of SSD
301314

315+
// ReverseOptions:
316+
ExpectNum int // expected number of reverse buildlets of this type
317+
302318
// Optional base env. GOROOT_BOOTSTRAP should go here if the buildlet
303319
// has Go 1.4+ baked in somewhere.
304320
env []string

0 commit comments

Comments
 (0)