// ingestBuild encapsulates many of the steps of ingesting a build: // - Record the mapping between the codename (build.Builder) and the internal target name. // - If no matching build exists, assign a new build number for this build and insert it. // - Otherwise, update the existing build to match the given build. func ingestBuild(build *buildbot.Build, commitHash, target string) error { // Store build.Builder (the codename) with its pair build.Target.Name in a local leveldb to serve redirects. if err := codenameDB.Put([]byte(build.Builder), []byte(target), nil); err != nil { glog.Errorf("Failed to write codename to data store: %s", err) } buildNumber, err := db.GetBuildNumberForCommit(build.Master, build.Builder, commitHash) if err != nil { return fmt.Errorf("Failed to find the build in the database: %s", err) } glog.Infof("GetBuildForCommit at hash: %s returned %d", commitHash, buildNumber) var cachedBuild *buildbot.Build if buildNumber != -1 { cachedBuild, err = db.GetBuildFromDB(build.Master, build.Builder, buildNumber) if err != nil { return fmt.Errorf("Failed to retrieve build from database: %s", err) } } if cachedBuild == nil { // This is a new build we've never seen before, so add it to the buildbot database. // TODO(benjaminwagner): This logic won't work well for concurrent requests. Revisit // after borenet's "giant datahopper change." // First calculate a new unique build.Number. number, err := db.GetMaxBuildNumber(build.Master, build.Builder) if err != nil { return fmt.Errorf("Failed to find next build number: %s", err) } build.Number = number + 1 glog.Infof("Writing new build to the database: %s %d", build.Builder, build.Number) } else { // If the state of the build has changed then write it to the buildbot database. glog.Infof("Writing updated build to the database: %s %d", build.Builder, build.Number) } if err := buildbot.IngestBuild(db, build, repos); err != nil { return fmt.Errorf("Failed to ingest build: %s", err) } return nil }
// 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 }
// step does a single step in ingesting builds from tradefed and pushing the results into the buildbot database. func step(targets []string, buildService *androidbuildinternal.Service, repos *gitinfo.RepoMap) { glog.Errorf("step: Begin") if err := repos.Update(); err != nil { glog.Errorf("Failed to update repos: %s", err) return } // Loop over every target and look for skia commits in the builds. for _, target := range targets { r, err := buildService.Build.List().Branch(SKIA_BRANCH).BuildType("submitted").Target(target).ExtraFields("changeInfo").MaxResults(40).Do() if err != nil { glog.Errorf("Failed to load internal builds: %v", err) continue } // Iterate over the builds in reverse order so we ingest the earlier Git // hashes first and the more recent Git hashes later. for i := len(r.Builds) - 1; i >= 0; i-- { b := r.Builds[i] commits := androidbuild.CommitsFromChanges(b.Changes) glog.Infof("Commits: %#v", commits) if len(commits) > 0 { var cachedBuild *buildbot.Build = nil // Only look at the first commit in the list. The commits always appear in reverse chronological order, so // the 0th entry is the most recent commit. c := commits[0] // Create a buildbot.Build from the build info. key, build := buildFromCommit(b, c) glog.Infof("Key: %s Hash: %s", key, c.Hash) // Store build.Builder (the codename) with its pair build.Target.Name in a local leveldb to serve redirects. if err := codenameDB.Put([]byte(build.Builder), []byte(b.Target.Name), nil); err != nil { glog.Errorf("Failed to write codename to data store: %s", err) } buildNumber, err := buildbot.GetBuildForCommit(build.Builder, build.Master, c.Hash) if err != nil { glog.Errorf("Failed to find the build in the database: %s", err) continue } glog.Infof("GetBuildForCommit at hash: %s returned %d", c.Hash, buildNumber) if buildNumber != -1 { cachedBuild, err = buildbot.GetBuildFromDB(build.Builder, build.Master, buildNumber) if err != nil { glog.Errorf("Failed to retrieve build from database: %s", err) continue } } if cachedBuild == nil { // This is a new build we've never seen before, so add it to the buildbot database. // First calculate a new unique build.Number. number, err := buildbot.GetMaxBuildNumber(build.Builder) if err != nil { glog.Infof("Failed to find next build number: %s", err) continue } build.Number = number + 1 if err := buildbot.IngestBuild(build, repos); err != nil { glog.Errorf("Failed to ingest build: %s", err) continue } cachedBuild = build } // If the state of the build has changed then write it to the buildbot database. if buildsDiffer(build, cachedBuild) { // If this was a failure then we need to check that there is a mirror // failure on the main branch, at which point we will say that this // is a warning. if build.Results == buildbot.BUILDBOT_FAILURE && brokenOnMaster(buildService, target, b.BuildId) { build.Results = buildbot.BUILDBOT_WARNING } cachedBuild.Results = build.Results cachedBuild.Finished = build.Finished glog.Infof("Writing updated build to the database: %s %d", cachedBuild.Builder, cachedBuild.Number) if err := buildbot.IngestBuild(cachedBuild, repos); err != nil { glog.Errorf("Failed to ingest build: %s", err) } } } liveness.Update() } } }
// buildsDiffer returns true if the given Build's have different finished or results status. func buildsDiffer(a, b *buildbot.Build) bool { return a.IsFinished() != b.IsFinished() || a.Results != b.Results }
// 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 }