Esempio n. 1
0
// 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
}
Esempio n. 2
0
// 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
}