// getBestCandidate finds the best BuildCandidate for the given builder. func (q *BuildQueue) getBestCandidate(bc *buildCache, recentCommits []string, now time.Time) (float64, *buildbot.Build, *buildbot.Build, error) { errMsg := fmt.Sprintf("Failed to get best candidate for %s: %%v", bc.Builder) repo, err := q.repos.Repo(bc.Repo) if err != nil { return 0.0, nil, nil, fmt.Errorf(errMsg, err) } // Find the current scores for each commit. currentScores := map[string]float64{} for _, commit := range recentCommits { currentBuild, err := bc.getBuildForCommit(commit) if err != nil { return 0.0, nil, nil, fmt.Errorf(errMsg, err) } d, err := repo.Details(commit) if err != nil { return 0.0, nil, nil, err } currentScores[commit] = scoreBuild(d, currentBuild, now, q.timeLambda) } // For each commit/builder pair, determine the score increase obtained // by running a build at that commit. scoreIncrease := map[string]float64{} newBuildsByCommit := map[string]*buildbot.Build{} stoleFromByCommit := map[string]*buildbot.Build{} for _, commit := range recentCommits { // Shortcut: Don't bisect builds with a huge number // of commits. This saves lots of time and only affects // the first successful build for a bot. b, err := bc.getBuildForCommit(commit) if err != nil { return 0.0, nil, nil, fmt.Errorf(errMsg, err) } if b != nil { if len(b.Commits) > NO_BISECT_COMMIT_LIMIT { glog.Warningf("Skipping %s on %s; previous build has too many commits (#%d)", commit[0:7], b.Builder, b.Number) scoreIncrease[commit] = 0.0 break // Don't bother looking at previous commits either, since these will be out of range. } } newScores := map[string]float64{} // Pretend to create a new Build at the given commit. newBuild := buildbot.Build{ Builder: bc.Builder, Master: bc.Master, Number: bc.MaxBuildNum + 1, GotRevision: commit, Repository: bc.Repo, } commits, stealFrom, stolen, err := buildbot.FindCommitsForBuild(bc, &newBuild, q.repos) if err != nil { return 0.0, nil, nil, fmt.Errorf(errMsg, err) } // Re-score all commits in the new build. newBuild.Commits = commits for _, c := range commits { d, err := repo.Details(c) if err != nil { return 0.0, nil, nil, fmt.Errorf(errMsg, err) } if _, ok := currentScores[c]; !ok { // If this build has commits which are outside of our window, // insert them into currentScores to account for them. b, err := bc.getBuildForCommit(c) if err != nil { return 0.0, nil, nil, fmt.Errorf(errMsg, err) } score := scoreBuild(d, b, now, q.timeLambda) currentScores[c] = score } newScores[c] = scoreBuild(d, &newBuild, now, q.timeLambda) } newBuildsByCommit[commit] = &newBuild // If the new build includes commits previously included in // another build, update scores for commits in the build we stole // them from. if stealFrom != -1 { stoleFromOrig, err := bc.getByNumber(stealFrom) if err != nil { return 0.0, nil, nil, fmt.Errorf(errMsg, err) } if stoleFromOrig == nil { // The build may not be cached. Fall back on getting it from the DB. stoleFromOrig, err = buildbot.GetBuildFromDB(bc.Builder, bc.Master, stealFrom) if err != nil { return 0.0, nil, nil, fmt.Errorf(errMsg, err) } if err := bc.Put(stoleFromOrig); err != nil { return 0.0, nil, nil, err } } // "copy" the build so that we can assign new commits to it // without modifying the cached build. stoleFromBuild := *stoleFromOrig newCommits := []string{} for _, c := range stoleFromBuild.Commits { if !util.In(c, stolen) { newCommits = append(newCommits, c) } } stoleFromBuild.Commits = newCommits for _, c := range stoleFromBuild.Commits { d, err := repo.Details(c) if err != nil { return 0.0, nil, nil, err } newScores[c] = scoreBuild(d, &stoleFromBuild, now, q.timeLambda) } stoleFromByCommit[commit] = &stoleFromBuild } // Sum the old and new scores. oldScoresList := make([]float64, 0, len(newScores)) newScoresList := make([]float64, 0, len(newScores)) for c, score := range newScores { oldScoresList = append(oldScoresList, currentScores[c]) newScoresList = append(newScoresList, score) } oldTotal := util.Float64StableSum(oldScoresList) newTotal := util.Float64StableSum(newScoresList) scoreIncrease[commit] = newTotal - oldTotal } // Arrange the score increases by builder. candidates := []*BuildCandidate{} for commit, increase := range scoreIncrease { candidates = append(candidates, &BuildCandidate{ Commit: commit, Score: increase, }) } sort.Sort(BuildCandidateSlice(candidates)) best := candidates[len(candidates)-1] return best.Score, newBuildsByCommit[best.Commit], stoleFromByCommit[best.Commit], nil }
// updateRepo syncs the given repo and returns a set of BuildCandidates for it. func (q *BuildQueue) updateRepo(repoUrl string, repo *gitinfo.GitInfo, now time.Time) (map[string][]*BuildCandidate, error) { from := now.Add(-q.period) if q.period == PERIOD_FOREVER { from = time.Unix(0, 0) } recentCommits := repo.From(from) commitDetails := map[string]*gitinfo.LongCommit{} for _, c := range recentCommits { details, err := repo.Details(c) if err != nil { return nil, err } commitDetails[c] = details } // Get all builds associated with the recent commits. buildsByCommit, err := buildbot.GetBuildsForCommits(recentCommits, map[int]bool{}) if err != nil { return nil, err } // Find the sets of all bots and masters, organize builds by // commit/builder and builder/number. masters := map[string]string{} builds := map[string]map[string]*buildbot.Build{} buildsByBuilderAndNum := map[string]map[int]*buildbot.Build{} for commit, buildsForCommit := range buildsByCommit { builds[commit] = map[string]*buildbot.Build{} for _, build := range buildsForCommit { if !util.In(build.Builder, q.botWhitelist) { continue } masters[build.Builder] = build.Master builds[commit][build.Builder] = build if _, ok := buildsByBuilderAndNum[build.Builder]; !ok { buildsByBuilderAndNum[build.Builder] = map[int]*buildbot.Build{} } buildsByBuilderAndNum[build.Builder][build.Number] = build } } allBots := make([]string, 0, len(masters)) for builder, _ := range masters { allBots = append(allBots, builder) } // Find the current scores for each commit/builder pair. currentScores := map[string]map[string]float64{} for _, commit := range recentCommits { myBuilds, ok := builds[commit] if !ok { myBuilds = map[string]*buildbot.Build{} } currentScores[commit] = map[string]float64{} for _, builder := range allBots { currentScores[commit][builder] = scoreBuild(commitDetails[commit], myBuilds[builder], now, q.timeLambda) } } // For each commit/builder pair, determine the score increase obtained // by running a build at that commit. scoreIncrease := map[string]map[string]float64{} for _, commit := range recentCommits { scoreIncrease[commit] = map[string]float64{} for _, builder := range allBots { // Shortcut: Don't bisect builds with a huge number // of commits. This saves lots of time and only affects // the first successful build for a bot. if _, ok := builds[commit][builder]; ok { if len(builds[commit][builder].Commits) > NO_BISECT_COMMIT_LIMIT { glog.Warningf("Skipping %s on %s; previous build has too many commits.", commit[0:7], builder) scoreIncrease[commit][builder] = 0.0 continue } } newScores := map[string]float64{} // Pretend to create a new Build at the given commit. newBuild := buildbot.Build{ Builder: builder, Master: masters[builder], Number: math.MaxInt32, GotRevision: commit, Repository: repoUrl, } commits, stealFrom, stolen, err := buildbot.FindCommitsForBuild(&newBuild, q.repos) if err != nil { return nil, err } // Re-score all commits in the new build. newBuild.Commits = commits for _, c := range commits { if _, ok := currentScores[c]; !ok { // If this build has commits which are outside of our window, // insert them into currentScores to account for them. score := scoreBuild(commitDetails[commit], builds[commit][builder], now, q.timeLambda) currentScores[c] = map[string]float64{ builder: score, } } if _, ok := commitDetails[c]; !ok { d, err := repo.Details(c) if err != nil { return nil, err } commitDetails[c] = d } newScores[c] = scoreBuild(commitDetails[c], &newBuild, now, q.timeLambda) } // If the new build includes commits previously included in // another build, update scores for commits in the build we stole // them from. if stealFrom != -1 { stoleFromOrig, ok := buildsByBuilderAndNum[builder][stealFrom] if !ok { // The build may not be cached. Fall back on getting it from the DB. stoleFromOrig, err = buildbot.GetBuildFromDB(builder, masters[builder], stealFrom) if err != nil { return nil, err } buildsByBuilderAndNum[builder][stealFrom] = stoleFromOrig } // "copy" the build so that we can assign new commits to it // without modifying the cached build. stoleFromBuild := *stoleFromOrig newCommits := []string{} for _, c := range stoleFromBuild.Commits { if !util.In(c, stolen) { newCommits = append(newCommits, c) } } stoleFromBuild.Commits = newCommits for _, c := range stoleFromBuild.Commits { newScores[c] = scoreBuild(commitDetails[c], &stoleFromBuild, now, q.timeLambda) } } // Sum the old and new scores. // First, sort the old and new scores to help with numerical stability. oldScoresList := make([]float64, 0, len(newScores)) newScoresList := make([]float64, 0, len(newScores)) for c, score := range newScores { oldScoresList = append(oldScoresList, currentScores[c][builder]) newScoresList = append(newScoresList, score) } sort.Sort(sort.Float64Slice(oldScoresList)) sort.Sort(sort.Float64Slice(newScoresList)) oldTotal := 0.0 newTotal := 0.0 for i, _ := range oldScoresList { oldTotal += oldScoresList[i] newTotal += newScoresList[i] } scoreIncrease[commit][builder] = newTotal - oldTotal } } // Arrange the score increases by builder. candidates := map[string][]*BuildCandidate{} for commit, builders := range scoreIncrease { for builder, scoreIncrease := range builders { if _, ok := candidates[builder]; !ok { candidates[builder] = []*BuildCandidate{} } // Don't schedule builds below the given threshold. if scoreIncrease > q.scoreThreshold { candidates[builder] = append(candidates[builder], &BuildCandidate{ Author: commitDetails[commit].Author, Builder: builder, Commit: commit, Repo: repoUrl, Score: scoreIncrease, }) } } } return candidates, nil }