golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/coordinator/schedule/schedule.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build linux || darwin 6 7 package schedule 8 9 import ( 10 "context" 11 "fmt" 12 "log" 13 "sort" 14 "sync" 15 "time" 16 17 "golang.org/x/build/buildlet" 18 "golang.org/x/build/dashboard" 19 "golang.org/x/build/internal/coordinator/pool" 20 "golang.org/x/build/internal/coordinator/pool/queue" 21 "golang.org/x/build/internal/spanlog" 22 "golang.org/x/build/types" 23 ) 24 25 // The Scheduler prioritizes access to buidlets. It accepts requests 26 // for buildlets, starts the creation of buildlets from BuildletPools, 27 // and prioritizes which callers gets them first when they're ready. 28 type Scheduler struct { 29 // mu guards the following fields. 30 mu sync.Mutex 31 32 // waiting contains all the set of callers who are waiting for 33 // a buildlet, keyed by the host type they're waiting for. 34 waiting map[string]map[*queue.SchedItem]bool // hostType -> item -> true 35 36 // hostsCreating is the number of GetBuildlet calls currently in flight 37 // to each hostType's respective buildlet pool. 38 hostsCreating map[string]int // hostType -> count 39 40 lastProgress map[string]time.Time // hostType -> time last delivered buildlet 41 } 42 43 // NewScheduler returns a new scheduler. 44 func NewScheduler() *Scheduler { 45 s := &Scheduler{ 46 hostsCreating: make(map[string]int), 47 waiting: make(map[string]map[*queue.SchedItem]bool), 48 lastProgress: make(map[string]time.Time), 49 } 50 return s 51 } 52 53 type stderrLogger struct{} 54 55 func (stderrLogger) LogEventTime(event string, optText ...string) { 56 if len(optText) == 0 { 57 log.Printf("sched.getbuildlet: %v", event) 58 } else { 59 log.Printf("sched.getbuildlet: %v, %v", event, optText[0]) 60 } 61 } 62 63 func (l stderrLogger) CreateSpan(event string, optText ...string) spanlog.Span { 64 return CreateSpan(l, event, optText...) 65 } 66 67 func (s *Scheduler) removeWaiter(si *queue.SchedItem) { 68 s.mu.Lock() 69 defer s.mu.Unlock() 70 if m := s.waiting[si.HostType]; m != nil { 71 delete(m, si) 72 } 73 } 74 75 func (s *Scheduler) addWaiter(si *queue.SchedItem) { 76 s.mu.Lock() 77 defer s.mu.Unlock() 78 if _, ok := s.waiting[si.HostType]; !ok { 79 s.waiting[si.HostType] = make(map[*queue.SchedItem]bool) 80 } 81 s.waiting[si.HostType][si] = true 82 } 83 84 func (s *Scheduler) hasWaiter(si *queue.SchedItem) bool { 85 s.mu.Lock() 86 defer s.mu.Unlock() 87 return s.waiting[si.HostType][si] 88 } 89 90 type SchedulerWaitingState struct { 91 Count int 92 Newest time.Duration 93 Oldest time.Duration 94 } 95 96 func (st *SchedulerWaitingState) add(si *queue.SchedItem) { 97 st.Count++ 98 age := time.Since(si.RequestTime).Round(time.Second) 99 if st.Newest == 0 || age < st.Newest { 100 st.Newest = age 101 } 102 if st.Oldest == 0 || age > st.Oldest { 103 st.Oldest = age 104 } 105 } 106 107 type SchedulerHostState struct { 108 HostType string 109 LastProgress time.Duration 110 Total SchedulerWaitingState 111 Gomote SchedulerWaitingState 112 Try SchedulerWaitingState 113 Regular SchedulerWaitingState 114 } 115 116 type SchedulerState struct { 117 HostTypes []SchedulerHostState 118 } 119 120 func (s *Scheduler) State() (st SchedulerState) { 121 s.mu.Lock() 122 defer s.mu.Unlock() 123 124 for hostType, m := range s.waiting { 125 if len(m) == 0 { 126 continue 127 } 128 var hst SchedulerHostState 129 hst.HostType = hostType 130 for si := range m { 131 hst.Total.add(si) 132 if si.IsGomote { 133 hst.Gomote.add(si) 134 } else if si.IsTry { 135 hst.Try.add(si) 136 } else { 137 hst.Regular.add(si) 138 } 139 } 140 if lp := s.lastProgress[hostType]; !lp.IsZero() { 141 lastProgressAgo := time.Since(lp) 142 if lastProgressAgo < hst.Total.Oldest { 143 hst.LastProgress = lastProgressAgo.Round(time.Second) 144 } 145 } 146 st.HostTypes = append(st.HostTypes, hst) 147 } 148 149 sort.Slice(st.HostTypes, func(i, j int) bool { return st.HostTypes[i].HostType < st.HostTypes[j].HostType }) 150 return st 151 } 152 153 // WaiterState returns tells waiter how many callers are on the line 154 // in front of them. 155 func (s *Scheduler) WaiterState(waiter *queue.SchedItem) (ws types.BuildletWaitStatus) { 156 s.mu.Lock() 157 defer s.mu.Unlock() 158 159 m := s.waiting[waiter.HostType] 160 for si := range m { 161 if si.Less(waiter) { 162 ws.Ahead++ 163 } 164 } 165 166 return ws 167 } 168 169 // GetBuildlet requests a buildlet with the parameters described in si. 170 // 171 // The provided si must be newly allocated; ownership passes to the scheduler. 172 func (s *Scheduler) GetBuildlet(ctx context.Context, si *queue.SchedItem) (buildlet.Client, error) { 173 hostConf, ok := dashboard.Hosts[si.HostType] 174 if !ok && pool.TestPoolHook == nil { 175 return nil, fmt.Errorf("invalid SchedItem.HostType %q", si.HostType) 176 } 177 si.RequestTime = time.Now() 178 179 s.addWaiter(si) 180 defer s.removeWaiter(si) 181 182 return pool.ForHost(hostConf).GetBuildlet(ctx, si.HostType, stderrLogger{}, si) 183 }