Chromium Code Reviews| Index: go/src/infra/tools/swarming-transition/cmd/swarmbucketcheck/main.go |
| diff --git a/go/src/infra/tools/swarming-transition/cmd/swarmbucketcheck/main.go b/go/src/infra/tools/swarming-transition/cmd/swarmbucketcheck/main.go |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..adf14a8644249be6943eb0359d70cb2196916684 |
| --- /dev/null |
| +++ b/go/src/infra/tools/swarming-transition/cmd/swarmbucketcheck/main.go |
| @@ -0,0 +1,184 @@ |
| +package main |
|
Vadim Sh.
2016/06/28 18:17:14
copyright
nodir
2016/06/28 22:08:57
Done.
|
| + |
| +import ( |
| + "flag" |
| + "fmt" |
| + "net/http" |
| + "os" |
| + "strings" |
| + "time" |
| + |
| + "github.com/luci/luci-go/common/api/buildbucket/buildbucket/v1" |
| +) |
| + |
| +var since = flag.Int64("since", 0, "analyze builds since this timestamp. Defaults to 10 days ago.") |
| +var bucket = flag.String("bucket", "", `buildbucket bucket name, e.g. "master.tryserver.infra"`) |
| +var builders = flag.String("builder", "", `comma-separated list of builder names without swarming suffix, e.g. "Infra Presubmit"`) |
| + |
| +const swarmingSuffix = " (Swarming)" |
| + |
| +func fetchBuilds(bucket, builder string, startingFrom time.Time) ([]*buildbucket.ApiBuildMessage, error) { |
| + client, err := buildbucket.New(http.DefaultClient) |
| + if err != nil { |
| + return nil, err |
| + } |
| + client.BasePath = "https://cr-buildbucket.appspot.com/_ah/api/buildbucket/v1/" |
| + |
| + req := client.Search() |
| + req.Bucket(bucket) |
| + req.Tag("builder:" + builder) |
| + req.Status("COMPLETED") |
| + req.MaxBuilds(100) |
| + |
| + var result []*buildbucket.ApiBuildMessage |
| + for { |
| + res, err := req.Do() |
| + if err != nil { |
| + return result, err |
| + } |
| + if res.Error != nil { |
| + return result, fmt.Errorf(res.Error.Message) |
| + } |
| + |
| + for _, b := range res.Builds { |
| + createdAt := time.Unix(b.CreatedTs/1000000, 0) |
| + if createdAt.Before(startingFrom) { |
| + return result, nil |
| + } |
| + result = append(result, b) |
| + } |
| + |
| + if len(res.Builds) == 0 || res.NextCursor == "" { |
| + break |
| + } |
| + req.StartCursor(res.NextCursor) |
| + } |
| + return result, nil |
| +} |
| + |
| +type buildSet struct { |
| + builds []*buildbucket.ApiBuildMessage |
| + bestResult string |
| +} |
| + |
| +// groupBuilds groups builds by buildset tag. |
| +func groupBuilds(builds []*buildbucket.ApiBuildMessage) map[string]*buildSet { |
| + results := map[string]*buildSet{} |
| + for _, b := range builds { |
| + tags := parseTags(b.Tags) |
| + buildSetName := tags["buildset"] |
| + if buildSetName == "" { |
| + fmt.Printf("skipped build %d: no buildset tag\n", b.Id) |
| + continue |
| + } |
| + set := results[buildSetName] |
| + if set == nil { |
| + set = &buildSet{} |
| + results[buildSetName] = set |
| + } |
| + |
| + set.builds = append(set.builds, b) |
| + if set.bestResult == "" || b.Result == "SUCCESS" { |
| + set.bestResult = b.Result |
| + } |
| + } |
| + return results |
| +} |
| + |
| +func run() error { |
| + flag.Parse() |
| + if *bucket == "" { |
| + return fmt.Errorf("bucket is not specified") |
| + } |
| + if *builders == "" { |
| + return fmt.Errorf("builders are not specified") |
| + } |
| + if len(flag.Args()) > 0 { |
| + return fmt.Errorf("unexpected arguments: %s", flag.Args()) |
| + } |
| + |
| + var startingFrom time.Time |
| + var duration time.Duration |
| + if *since == 0 { |
| + duration = 240 * time.Hour |
| + startingFrom = time.Now().Add(-duration) |
| + } else { |
| + startingFrom = time.Unix(*since, 0) |
| + duration = time.Since(startingFrom) |
| + } |
| + |
| + for i, builder := range strings.Split(*builders, ",") { |
| + builder = strings.TrimSpace(builder) |
| + if builder == "" { |
| + continue |
| + } |
| + |
| + if i > 0 { |
| + fmt.Println() |
| + } |
| + fmt.Printf("builder %q\n", builder) |
| + fmt.Printf("searching for all builds since timestamp %d till %d...\n", |
| + startingFrom.Unix(), time.Now().Unix()) |
| + // We will actually fetch builds after after time.Now too, but it is fine. |
| + swarmingBuilds, err := fetchBuilds(*bucket, builder+swarmingSuffix, startingFrom) |
| + if err != nil { |
| + return fmt.Errorf("could not fetch builds: %s", err) |
| + } |
| + if len(swarmingBuilds) == 0 { |
| + fmt.Printf("no swarming builds for builder %q\n", builder) |
| + continue |
| + } |
| + buildbotBuilds, err := fetchBuilds(*bucket, builder, startingFrom) |
| + if err != nil { |
| + return fmt.Errorf("could not fetch builds: %s", err) |
| + } |
| + |
| + swarmingBuildSets := groupBuilds(swarmingBuilds) |
| + buildbotBuildSets := groupBuilds(buildbotBuilds) |
| + |
| + consistentN := 0 |
| + inconsistentN := 0 |
| + for setName, swarmingSet := range swarmingBuildSets { |
| + buildbotSet := buildbotBuildSets[setName] |
| + if buildbotSet == nil { |
| + fmt.Printf("no buildbot builds for buildset %s\n", setName) |
| + continue |
| + } |
| + if buildbotSet.bestResult == swarmingSet.bestResult { |
| + consistentN++ |
| + continue |
| + } |
| + inconsistentN++ |
| + |
| + fmt.Printf("%s is inconsistent\n", setName) |
| + for _, b := range swarmingSet.builds { |
| + fmt.Printf(" %s %s\n", b.Result, b.Url) |
| + } |
| + for _, b := range buildbotSet.builds { |
| + fmt.Printf(" %s %s\n", b.Result, b.Url) |
| + } |
| + } |
| + |
| + fmt.Printf("%0.2f%% consistent build sets, %d buildbot builds, %d swarming builds\n", |
| + 100*float64(consistentN)/float64(consistentN+inconsistentN), len(buildbotBuilds), len(swarmingBuilds)) |
| + } |
| + return nil |
| +} |
| + |
| +func main() { |
| + if err := run(); err != nil { |
| + fmt.Fprintln(os.Stderr, err) |
| + os.Exit(1) |
| + } |
| +} |
| + |
| +func parseTags(tags []string) map[string]string { |
| + result := make(map[string]string, len(tags)) |
| + for _, t := range tags { |
| + parts := strings.SplitN(t, ":", 2) |
| + if len(parts) == 2 { |
| + result[parts[0]] = parts[1] |
| + } |
| + } |
| + return result |
| +} |