github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/scheduler/task_prioritizer.go (about) 1 package scheduler 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/evergreen-ci/evergreen" 8 "github.com/evergreen-ci/evergreen/model/task" 9 "github.com/mongodb/grip" 10 "github.com/pkg/errors" 11 ) 12 13 // TaskPrioritizer is responsible for taking in a slice of tasks, and ordering them 14 // according to which should be run first. 15 type TaskPrioritizer interface { 16 // Takes in a slice of tasks and the current MCI settings. 17 // Returns the slice of tasks, sorted in the order in which they should 18 // be run, as well as an error if appropriate. 19 PrioritizeTasks(settings *evergreen.Settings, tasks []task.Task) ( 20 []task.Task, error) 21 } 22 23 // CmpBasedTaskComparator runs the tasks through a slice of comparator functions 24 // determining which is more important. 25 type CmpBasedTaskComparator struct { 26 tasks []task.Task 27 errsDuringSort []error 28 setupFuncs []sortSetupFunc 29 comparators []taskPriorityCmp 30 31 // caches for sorting 32 previousTasksCache map[string]task.Task 33 34 // cache the number of tasks that have failed in other buildvariants; tasks 35 // with the same revision, project, display name and requester 36 similarFailingCount map[string]int 37 } 38 39 // CmpBasedTaskQueues represents the three types of queues that are created for merging together into one queue. 40 // The HighPriorityTasks list represent the tasks that are always placed at the front of the queue 41 // PatchTasks and RepotrackerTasks are interleaved after the high priority tasks. 42 type CmpBasedTaskQueues struct { 43 HighPriorityTasks []task.Task 44 PatchTasks []task.Task 45 RepotrackerTasks []task.Task 46 } 47 48 // NewCmpBasedTaskComparator returns a new task prioritizer, using the default set of comparators 49 // as well as the setup functions necessary for those comparators. 50 func NewCmpBasedTaskComparator() *CmpBasedTaskComparator { 51 return &CmpBasedTaskComparator{ 52 setupFuncs: []sortSetupFunc{ 53 cachePreviousTasks, 54 cacheSimilarFailing, 55 }, 56 comparators: []taskPriorityCmp{ 57 byPriority, 58 byNumDeps, 59 byRevisionOrderNumber, 60 byCreateTime, 61 bySimilarFailing, 62 byRecentlyFailing, 63 }, 64 } 65 } 66 67 type CmpBasedTaskPrioritizer struct{} 68 69 // PrioritizeTask prioritizes the tasks to run. First splits the tasks into slices based on 70 // whether they are part of patch versions or automatically created versions. 71 // Then prioritizes each slice, and merges them. 72 // Returns a full slice of the prioritized tasks, and an error if one occurs. 73 func (prioritizer *CmpBasedTaskPrioritizer) PrioritizeTasks( 74 settings *evergreen.Settings, tasks []task.Task) ([]task.Task, error) { 75 76 comparator := NewCmpBasedTaskComparator() 77 // split the tasks into repotracker tasks and patch tasks, then prioritize 78 // individually and merge 79 taskQueues := comparator.splitTasksByRequester(tasks) 80 prioritizedTaskLists := make([][]task.Task, 0, 3) 81 for _, taskList := range [][]task.Task{taskQueues.RepotrackerTasks, taskQueues.PatchTasks, taskQueues.HighPriorityTasks} { 82 83 comparator.tasks = taskList 84 85 err := comparator.setupForSortingTasks() 86 if err != nil { 87 return nil, errors.Wrap(err, "Error running setup for sorting tasks") 88 } 89 90 sort.Sort(comparator) 91 92 if len(comparator.errsDuringSort) > 0 { 93 errString := "The following errors were thrown while sorting:" 94 for _, e := range comparator.errsDuringSort { 95 errString += fmt.Sprintf("\n %v", e) 96 } 97 return nil, errors.New(errString) 98 } 99 100 prioritizedTaskLists = append(prioritizedTaskLists, comparator.tasks) 101 } 102 prioritizedTaskQueues := CmpBasedTaskQueues{ 103 RepotrackerTasks: prioritizedTaskLists[0], 104 PatchTasks: prioritizedTaskLists[1], 105 HighPriorityTasks: prioritizedTaskLists[2], 106 } 107 108 comparator.tasks = comparator.mergeTasks(settings, &prioritizedTaskQueues) 109 110 return comparator.tasks, nil 111 } 112 113 // Run all of the setup functions necessary for prioritizing the tasks. 114 // Returns an error if any of the setup funcs return an error. 115 func (self *CmpBasedTaskComparator) setupForSortingTasks() error { 116 for _, setupFunc := range self.setupFuncs { 117 if err := setupFunc(self); err != nil { 118 return errors.Wrap(err, "Error running setup for sorting") 119 } 120 } 121 return nil 122 } 123 124 // Determine which of two tasks is more important, by running the tasks through 125 // the comparator functions and returning the first definitive decision on which 126 // is more important. 127 func (self *CmpBasedTaskComparator) taskMoreImportantThan(task1, 128 task2 task.Task) (bool, error) { 129 130 // run through the comparators, and return the first definitive decision on 131 // which task is more important 132 for _, cmp := range self.comparators { 133 ret, err := cmp(task1, task2, self) 134 if err != nil { 135 return false, errors.WithStack(err) 136 } 137 switch ret { 138 case -1: 139 return false, nil 140 case 0: 141 continue 142 case 1: 143 return true, nil 144 default: 145 panic("Unexpected return value from task comparator") 146 } 147 } 148 149 // none of the comparators reached a definitive decision, so the return val 150 // doesn't matter 151 return false, nil 152 } 153 154 // Functions that ensure the CmdBasedTaskPrioritizer implements sort.Interface 155 156 func (self *CmpBasedTaskComparator) Len() int { 157 return len(self.tasks) 158 } 159 160 func (self *CmpBasedTaskComparator) Less(i, j int) bool { 161 moreImportant, err := self.taskMoreImportantThan(self.tasks[i], 162 self.tasks[j]) 163 if err != nil { 164 self.errsDuringSort = append(self.errsDuringSort, err) 165 } 166 return moreImportant 167 } 168 169 func (self *CmpBasedTaskComparator) Swap(i, j int) { 170 self.tasks[i], self.tasks[j] = self.tasks[j], self.tasks[i] 171 } 172 173 // Split the tasks, based on the requester field. 174 // Returns two slices - the tasks requested by the repotracker, and the tasks 175 // requested in a patch. 176 func (self *CmpBasedTaskComparator) splitTasksByRequester( 177 allTasks []task.Task) *CmpBasedTaskQueues { 178 179 repoTrackerTasks := make([]task.Task, 0, len(allTasks)) 180 patchTasks := make([]task.Task, 0, len(allTasks)) 181 priorityTasks := make([]task.Task, 0, len(allTasks)) 182 183 for _, task := range allTasks { 184 switch { 185 case task.Priority > evergreen.MaxTaskPriority: 186 priorityTasks = append(priorityTasks, task) 187 case task.Requester == evergreen.RepotrackerVersionRequester: 188 repoTrackerTasks = append(repoTrackerTasks, task) 189 case task.Requester == evergreen.PatchVersionRequester: 190 patchTasks = append(patchTasks, task) 191 default: 192 grip.Errorf("Unrecognized requester '%s' for task %s", task.Requester, task.Id) 193 } 194 } 195 196 return &CmpBasedTaskQueues{ 197 HighPriorityTasks: priorityTasks, 198 RepotrackerTasks: repoTrackerTasks, 199 PatchTasks: patchTasks, 200 } 201 } 202 203 // Merge the slices of tasks requested by the repotracker and in patches. 204 // Returns a slice of the merged tasks. 205 func (self *CmpBasedTaskComparator) mergeTasks(settings *evergreen.Settings, 206 tq *CmpBasedTaskQueues) []task.Task { 207 208 mergedTasks := make([]task.Task, 0, len(tq.RepotrackerTasks)+ 209 len(tq.PatchTasks)+len(tq.HighPriorityTasks)) 210 211 toggle := settings.Scheduler.MergeToggle 212 if toggle == 0 { 213 toggle = 2 // defaults to interleaving evenly 214 } 215 216 rIdx := 0 217 pIdx := 0 218 lenRepoTrackerTasks := len(tq.RepotrackerTasks) 219 lenPatchTasks := len(tq.PatchTasks) 220 221 // add the high priority tasks to the start of the queue 222 mergedTasks = append(mergedTasks, tq.HighPriorityTasks...) 223 for idx := 0; idx < len(tq.RepotrackerTasks)+len(tq.PatchTasks); idx++ { 224 if pIdx >= lenPatchTasks { // overruns patch tasks 225 mergedTasks = append(mergedTasks, tq.RepotrackerTasks[rIdx]) 226 rIdx++ 227 } else if rIdx >= lenRepoTrackerTasks { // overruns repotracker tasks 228 mergedTasks = append(mergedTasks, tq.PatchTasks[pIdx]) 229 pIdx++ 230 } else if idx > 0 && (idx+1)%toggle == 0 { // turn for a repotracker task 231 mergedTasks = append(mergedTasks, tq.RepotrackerTasks[rIdx]) 232 rIdx++ 233 } else { // turn for a patch task 234 mergedTasks = append(mergedTasks, tq.PatchTasks[pIdx]) 235 pIdx++ 236 } 237 } 238 return mergedTasks 239 }