| OLD | NEW |
| 1 package buildbot | 1 package buildbot |
| 2 | 2 |
| 3 import ( | 3 import ( |
| 4 "encoding/json" | 4 "encoding/json" |
| 5 "fmt" | 5 "fmt" |
| 6 "net/http" | 6 "net/http" |
| 7 "sync" | 7 "sync" |
| 8 "time" | 8 "time" |
| 9 |
| 10 "skia.googlesource.com/buildbot.git/go/gitinfo" |
| 9 ) | 11 ) |
| 10 | 12 |
| 11 var ( | 13 var ( |
| 12 // TODO(borenet): Avoid hard-coding this list. Instead, obtain it from | 14 // TODO(borenet): Avoid hard-coding this list. Instead, obtain it from |
| 13 // checked-in code or the set of masters which are actually running. | 15 // checked-in code or the set of masters which are actually running. |
| 14 MASTER_NAMES = []string{"client.skia", "client.skia.android", "client.sk
ia.compile", "client.skia.fyi"} | 16 MASTER_NAMES = []string{"client.skia", "client.skia.android", "client.sk
ia.compile", "client.skia.fyi"} |
| 15 httpGet = http.Get | 17 httpGet = http.Get |
| 16 ) | 18 ) |
| 17 | 19 |
| 18 // get loads data from a buildbot JSON endpoint. | 20 // get loads data from a buildbot JSON endpoint. |
| 19 func get(url string, rv interface{}) error { | 21 func get(url string, rv interface{}) error { |
| 20 resp, err := httpGet(url) | 22 resp, err := httpGet(url) |
| 21 if err != nil { | 23 if err != nil { |
| 22 return fmt.Errorf("Failed to GET %s: %v", url, err) | 24 return fmt.Errorf("Failed to GET %s: %v", url, err) |
| 23 } | 25 } |
| 24 dec := json.NewDecoder(resp.Body) | 26 dec := json.NewDecoder(resp.Body) |
| 25 if err := dec.Decode(rv); err != nil { | 27 if err := dec.Decode(rv); err != nil { |
| 26 return fmt.Errorf("Failed to decode JSON: %v", err) | 28 return fmt.Errorf("Failed to decode JSON: %v", err) |
| 27 } | 29 } |
| 28 return nil | 30 return nil |
| 29 } | 31 } |
| 30 | 32 |
| 33 // findCommitsRecursive is a recursive function called by findCommitsForBuild. |
| 34 // It traces the history to find builds which were first included in the given |
| 35 // build. |
| 36 func findCommitsRecursive(b *Build, hash string, repo *gitinfo.GitInfo) ([]strin
g, error) { |
| 37 // Shortcut for empty hashes. This can happen when a commit has no |
| 38 // parents (initial commit) or when a Build has no GotRevision. |
| 39 if hash == "" { |
| 40 return []string{}, nil |
| 41 } |
| 42 |
| 43 // Determine whether any build already includes this commit. |
| 44 n, err := GetBuildForCommit(b.MasterName, b.BuilderName, hash) |
| 45 if err != nil { |
| 46 return nil, fmt.Errorf("Could not find build for commit %s: %v",
hash, err) |
| 47 } |
| 48 // If so, stop. |
| 49 if n >= 0 { |
| 50 return []string{}, nil |
| 51 } |
| 52 |
| 53 // Recurse on the commit's parents. |
| 54 c, err := repo.Details(hash) |
| 55 if err != nil { |
| 56 return nil, fmt.Errorf("Failed to obtain details for %s: %v", ha
sh, err) |
| 57 } |
| 58 commits := []string{hash} |
| 59 for _, p := range c.Parents { |
| 60 moreCommits, err := findCommitsRecursive(b, p, repo) |
| 61 if err != nil { |
| 62 return nil, err |
| 63 } |
| 64 commits = append(commits, moreCommits...) |
| 65 } |
| 66 return commits, nil |
| 67 } |
| 68 |
| 69 // findCommitsForBuild determines which commits were first included in the |
| 70 // given build. Assumes that all previous builds for the given builder/master |
| 71 // are already in the database. |
| 72 func findCommitsForBuild(b *Build, repo *gitinfo.GitInfo) ([]string, error) { |
| 73 return findCommitsRecursive(b, b.GotRevision, repo) |
| 74 } |
| 75 |
| 31 // getBuildFromMaster retrieves the given build from the build master's JSON | 76 // getBuildFromMaster retrieves the given build from the build master's JSON |
| 32 // interface as specified by the master, builder, and build number. | 77 // interface as specified by the master, builder, and build number. |
| 33 func getBuildFromMaster(master, builder string, buildNumber int) (*Build, error)
{ | 78 func getBuildFromMaster(master, builder string, buildNumber int, repo *gitinfo.G
itInfo) (*Build, error) { |
| 34 var build Build | 79 var build Build |
| 35 url := fmt.Sprintf("%s%s/json/builders/%s/builds/%d", BUILDBOT_URL, mast
er, builder, buildNumber) | 80 url := fmt.Sprintf("%s%s/json/builders/%s/builds/%d", BUILDBOT_URL, mast
er, builder, buildNumber) |
| 36 err := get(url, &build) | 81 err := get(url, &build) |
| 37 if err != nil { | 82 if err != nil { |
| 38 return nil, fmt.Errorf("Failed to retrieve build #%v for %v: %v"
, buildNumber, builder, err) | 83 return nil, fmt.Errorf("Failed to retrieve build #%v for %v: %v"
, buildNumber, builder, err) |
| 39 } | 84 } |
| 40 build.Branch = build.branch() | 85 build.Branch = build.branch() |
| 41 build.GotRevision = build.gotRevision() | 86 build.GotRevision = build.gotRevision() |
| 42 build.MasterName = master | 87 build.MasterName = master |
| 43 slaveProp := build.GetProperty("slavename").([]interface{}) | 88 slaveProp := build.GetProperty("slavename").([]interface{}) |
| (...skipping 18 matching lines...) Expand all Loading... |
| 62 s.ResultsRaw[0] = 0.0 | 107 s.ResultsRaw[0] = 0.0 |
| 63 } | 108 } |
| 64 s.Results = int(s.ResultsRaw[0].(float64)) | 109 s.Results = int(s.ResultsRaw[0].(float64)) |
| 65 } else { | 110 } else { |
| 66 s.Results = 0 | 111 s.Results = 0 |
| 67 } | 112 } |
| 68 s.Started = s.Times[0] | 113 s.Started = s.Times[0] |
| 69 s.Finished = s.Times[1] | 114 s.Finished = s.Times[1] |
| 70 } | 115 } |
| 71 | 116 |
| 72 » // TODO(borenet): Actually find the commits for this build. | 117 » // Find the commits for this build. |
| 73 » build.Commits = []string{} | 118 » commits, err := findCommitsForBuild(&build, repo) |
| 119 » if err != nil { |
| 120 » » return nil, fmt.Errorf("Could not find commits for build: %v", e
rr) |
| 121 » } |
| 122 » build.Commits = commits |
| 74 | 123 |
| 75 return &build, nil | 124 return &build, nil |
| 76 } | 125 } |
| 77 | 126 |
| 78 // GetBuildFromMaster retrieves the given build from the build master's JSON | 127 // retryGetBuildFromMaster retrieves the given build from the build master's JSO
N |
| 79 // interface as specified by the master, builder, and build number. Makes | 128 // interface as specified by the master, builder, and build number. Makes |
| 80 // multiple attempts in case the master fails to respond. | 129 // multiple attempts in case the master fails to respond. |
| 81 func GetBuildFromMaster(master, builder string, buildNumber int) (*Build, error)
{ | 130 func retryGetBuildFromMaster(master, builder string, buildNumber int, repo *giti
nfo.GitInfo) (*Build, error) { |
| 82 var b *Build | 131 var b *Build |
| 83 var err error | 132 var err error |
| 84 for attempt := 0; attempt < 3; attempt++ { | 133 for attempt := 0; attempt < 3; attempt++ { |
| 85 » » b, err = getBuildFromMaster(master, builder, buildNumber) | 134 » » b, err = getBuildFromMaster(master, builder, buildNumber, repo) |
| 86 if err == nil { | 135 if err == nil { |
| 87 break | 136 break |
| 88 } | 137 } |
| 89 time.Sleep(500 * time.Millisecond) | 138 time.Sleep(500 * time.Millisecond) |
| 90 } | 139 } |
| 91 return b, err | 140 return b, err |
| 92 } | 141 } |
| 93 | 142 |
| 94 // IngestBuild retrieves the given build from the build master's JSON interface | 143 // IngestBuild retrieves the given build from the build master's JSON interface |
| 95 // and pushes it into the database. | 144 // and pushes it into the database. |
| 96 func IngestBuild(master, builder string, buildNumber int) error { | 145 func IngestBuild(master, builder string, buildNumber int, repo *gitinfo.GitInfo)
error { |
| 97 » b, err := GetBuildFromMaster(master, builder, buildNumber) | 146 » b, err := retryGetBuildFromMaster(master, builder, buildNumber, repo) |
| 98 if err != nil { | 147 if err != nil { |
| 99 return fmt.Errorf("Failed to load build from master: %v", err) | 148 return fmt.Errorf("Failed to load build from master: %v", err) |
| 100 } | 149 } |
| 101 return b.ReplaceIntoDB() | 150 return b.ReplaceIntoDB() |
| 102 } | 151 } |
| 103 | 152 |
| 104 // getLatestBuilds returns a map whose keys are master names and values are | 153 // getLatestBuilds returns a map whose keys are master names and values are |
| 105 // sub-maps whose keys are builder names and values are build numbers | 154 // sub-maps whose keys are builder names and values are build numbers |
| 106 // representing the newest build for each builder/master pair. | 155 // representing the newest build for each builder/master pair. |
| 107 func getLatestBuilds() (map[string]map[string]int, error) { | 156 func getLatestBuilds() (map[string]map[string]int, error) { |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 196 } | 245 } |
| 197 } | 246 } |
| 198 if len(masterMap) > 0 { | 247 if len(masterMap) > 0 { |
| 199 unprocessed[m] = masterMap | 248 unprocessed[m] = masterMap |
| 200 } | 249 } |
| 201 } | 250 } |
| 202 return unprocessed, nil | 251 return unprocessed, nil |
| 203 } | 252 } |
| 204 | 253 |
| 205 // IngestNewBuilds finds the set of uningested builds and ingests them. | 254 // IngestNewBuilds finds the set of uningested builds and ingests them. |
| 206 func IngestNewBuilds() error { | 255 func IngestNewBuilds(repo *gitinfo.GitInfo) error { |
| 207 // TODO(borenet): Investigate the use of channels here. We should be | 256 // TODO(borenet): Investigate the use of channels here. We should be |
| 208 // able to start ingesting builds as the data becomes available rather | 257 // able to start ingesting builds as the data becomes available rather |
| 209 // than waiting until the end. | 258 // than waiting until the end. |
| 210 buildsToProcess, err := getUningestedBuilds() | 259 buildsToProcess, err := getUningestedBuilds() |
| 211 if err != nil { | 260 if err != nil { |
| 212 return fmt.Errorf("Failed to obtain the set of uningested builds
: %v", err) | 261 return fmt.Errorf("Failed to obtain the set of uningested builds
: %v", err) |
| 213 } | 262 } |
| 214 unfinished, err := getUnfinishedBuilds() | 263 unfinished, err := getUnfinishedBuilds() |
| 215 if err != nil { | 264 if err != nil { |
| 216 return fmt.Errorf("Failed to obtain the set of unfinished builds
: %v", err) | 265 return fmt.Errorf("Failed to obtain the set of unfinished builds
: %v", err) |
| 217 } | 266 } |
| 218 for _, b := range unfinished { | 267 for _, b := range unfinished { |
| 219 if _, ok := buildsToProcess[b.MasterName]; !ok { | 268 if _, ok := buildsToProcess[b.MasterName]; !ok { |
| 220 buildsToProcess[b.MasterName] = map[string][]int{} | 269 buildsToProcess[b.MasterName] = map[string][]int{} |
| 221 } | 270 } |
| 222 if _, ok := buildsToProcess[b.BuilderName]; !ok { | 271 if _, ok := buildsToProcess[b.BuilderName]; !ok { |
| 223 buildsToProcess[b.MasterName][b.BuilderName] = []int{} | 272 buildsToProcess[b.MasterName][b.BuilderName] = []int{} |
| 224 } | 273 } |
| 225 buildsToProcess[b.MasterName][b.BuilderName] = append(buildsToPr
ocess[b.MasterName][b.BuilderName], b.Number) | 274 buildsToProcess[b.MasterName][b.BuilderName] = append(buildsToPr
ocess[b.MasterName][b.BuilderName], b.Number) |
| 226 } | 275 } |
| 227 // TODO(borenet): Figure out how much of this is safe to parallelize. | 276 // TODO(borenet): Figure out how much of this is safe to parallelize. |
| 228 // We can definitely do different masters in parallel, and maybe we can | 277 // We can definitely do different masters in parallel, and maybe we can |
| 229 // ingest different builders in parallel as well. | 278 // ingest different builders in parallel as well. |
| 230 for m, v := range buildsToProcess { | 279 for m, v := range buildsToProcess { |
| 231 for b, w := range v { | 280 for b, w := range v { |
| 232 for _, n := range w { | 281 for _, n := range w { |
| 233 » » » » if err := IngestBuild(m, b, n); err != nil { | 282 » » » » if err := IngestBuild(m, b, n, repo); err != nil
{ |
| 234 return fmt.Errorf("Failed to ingest buil
d: %v", err) | 283 return fmt.Errorf("Failed to ingest buil
d: %v", err) |
| 235 } | 284 } |
| 236 } | 285 } |
| 237 } | 286 } |
| 238 } | 287 } |
| 239 return nil | 288 return nil |
| 240 } | 289 } |
| OLD | NEW |