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  }