| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package main |
| 6 |
| 7 import ( |
| 8 "flag" |
| 9 "fmt" |
| 10 "net/http" |
| 11 "os" |
| 12 "strings" |
| 13 "time" |
| 14 |
| 15 "github.com/luci/luci-go/common/api/buildbucket/buildbucket/v1" |
| 16 ) |
| 17 |
| 18 var since = flag.Int64("since", 0, "analyze builds since this timestamp. Default
s to 10 days ago.") |
| 19 var bucket = flag.String("bucket", "", `buildbucket bucket name, e.g. "master.tr
yserver.infra"`) |
| 20 var builders = flag.String("builder", "", `comma-separated list of builder names
without swarming suffix, e.g. "Infra Presubmit"`) |
| 21 |
| 22 const swarmingSuffix = " (Swarming)" |
| 23 |
| 24 func fetchBuilds(bucket, builder string, startingFrom time.Time) ([]*buildbucket
.ApiBuildMessage, error) { |
| 25 client, err := buildbucket.New(http.DefaultClient) |
| 26 if err != nil { |
| 27 return nil, err |
| 28 } |
| 29 client.BasePath = "https://cr-buildbucket.appspot.com/_ah/api/buildbucke
t/v1/" |
| 30 |
| 31 req := client.Search() |
| 32 req.Bucket(bucket) |
| 33 req.Tag("builder:" + builder) |
| 34 req.Status("COMPLETED") |
| 35 req.MaxBuilds(100) |
| 36 |
| 37 var result []*buildbucket.ApiBuildMessage |
| 38 for { |
| 39 res, err := req.Do() |
| 40 if err != nil { |
| 41 return result, err |
| 42 } |
| 43 if res.Error != nil { |
| 44 return result, fmt.Errorf(res.Error.Message) |
| 45 } |
| 46 |
| 47 for _, b := range res.Builds { |
| 48 createdAt := time.Unix(b.CreatedTs/1000000, 0) |
| 49 if createdAt.Before(startingFrom) { |
| 50 return result, nil |
| 51 } |
| 52 result = append(result, b) |
| 53 } |
| 54 |
| 55 if len(res.Builds) == 0 || res.NextCursor == "" { |
| 56 break |
| 57 } |
| 58 req.StartCursor(res.NextCursor) |
| 59 } |
| 60 return result, nil |
| 61 } |
| 62 |
| 63 type buildSet struct { |
| 64 builds []*buildbucket.ApiBuildMessage |
| 65 bestResult string |
| 66 } |
| 67 |
| 68 // groupBuilds groups builds by buildset tag. |
| 69 func groupBuilds(builds []*buildbucket.ApiBuildMessage) map[string]*buildSet { |
| 70 results := map[string]*buildSet{} |
| 71 for _, b := range builds { |
| 72 tags := parseTags(b.Tags) |
| 73 buildSetName := tags["buildset"] |
| 74 if buildSetName == "" { |
| 75 fmt.Printf("skipped build %d: no buildset tag\n", b.Id) |
| 76 continue |
| 77 } |
| 78 set := results[buildSetName] |
| 79 if set == nil { |
| 80 set = &buildSet{} |
| 81 results[buildSetName] = set |
| 82 } |
| 83 |
| 84 set.builds = append(set.builds, b) |
| 85 if set.bestResult == "" || b.Result == "SUCCESS" { |
| 86 set.bestResult = b.Result |
| 87 } |
| 88 } |
| 89 return results |
| 90 } |
| 91 |
| 92 func run() error { |
| 93 flag.Parse() |
| 94 if *bucket == "" { |
| 95 return fmt.Errorf("bucket is not specified") |
| 96 } |
| 97 if *builders == "" { |
| 98 return fmt.Errorf("builders are not specified") |
| 99 } |
| 100 if len(flag.Args()) > 0 { |
| 101 return fmt.Errorf("unexpected arguments: %s", flag.Args()) |
| 102 } |
| 103 |
| 104 var startingFrom time.Time |
| 105 var duration time.Duration |
| 106 if *since == 0 { |
| 107 duration = 240 * time.Hour |
| 108 startingFrom = time.Now().Add(-duration) |
| 109 } else { |
| 110 startingFrom = time.Unix(*since, 0) |
| 111 duration = time.Since(startingFrom) |
| 112 } |
| 113 |
| 114 for i, builder := range strings.Split(*builders, ",") { |
| 115 builder = strings.TrimSpace(builder) |
| 116 if builder == "" { |
| 117 continue |
| 118 } |
| 119 |
| 120 if i > 0 { |
| 121 fmt.Println() |
| 122 } |
| 123 fmt.Printf("builder %q\n", builder) |
| 124 fmt.Printf("searching for all builds since timestamp %d till %d.
..\n", |
| 125 startingFrom.Unix(), time.Now().Unix()) |
| 126 // We will actually fetch builds after after time.Now too, but i
t is fine. |
| 127 swarmingBuilds, err := fetchBuilds(*bucket, builder+swarmingSuff
ix, startingFrom) |
| 128 if err != nil { |
| 129 return fmt.Errorf("could not fetch builds: %s", err) |
| 130 } |
| 131 if len(swarmingBuilds) == 0 { |
| 132 fmt.Printf("no swarming builds for builder %q\n", builde
r) |
| 133 continue |
| 134 } |
| 135 buildbotBuilds, err := fetchBuilds(*bucket, builder, startingFro
m) |
| 136 if err != nil { |
| 137 return fmt.Errorf("could not fetch builds: %s", err) |
| 138 } |
| 139 |
| 140 swarmingBuildSets := groupBuilds(swarmingBuilds) |
| 141 buildbotBuildSets := groupBuilds(buildbotBuilds) |
| 142 |
| 143 consistentN := 0 |
| 144 inconsistentN := 0 |
| 145 for setName, swarmingSet := range swarmingBuildSets { |
| 146 buildbotSet := buildbotBuildSets[setName] |
| 147 if buildbotSet == nil { |
| 148 fmt.Printf("no buildbot builds for buildset %s\n
", setName) |
| 149 continue |
| 150 } |
| 151 if buildbotSet.bestResult == swarmingSet.bestResult { |
| 152 consistentN++ |
| 153 continue |
| 154 } |
| 155 inconsistentN++ |
| 156 |
| 157 fmt.Printf("%s is inconsistent\n", setName) |
| 158 for _, b := range swarmingSet.builds { |
| 159 fmt.Printf(" %s %s\n", b.Result, b.Url) |
| 160 } |
| 161 for _, b := range buildbotSet.builds { |
| 162 fmt.Printf(" %s %s\n", b.Result, b.Url) |
| 163 } |
| 164 } |
| 165 |
| 166 fmt.Printf("%0.2f%% consistent build sets, %d buildbot builds, %
d swarming builds\n", |
| 167 100*float64(consistentN)/float64(consistentN+inconsisten
tN), len(buildbotBuilds), len(swarmingBuilds)) |
| 168 } |
| 169 return nil |
| 170 } |
| 171 |
| 172 func main() { |
| 173 if err := run(); err != nil { |
| 174 fmt.Fprintln(os.Stderr, err) |
| 175 os.Exit(1) |
| 176 } |
| 177 } |
| 178 |
| 179 func parseTags(tags []string) map[string]string { |
| 180 result := make(map[string]string, len(tags)) |
| 181 for _, t := range tags { |
| 182 parts := strings.SplitN(t, ":", 2) |
| 183 if len(parts) == 2 { |
| 184 result[parts[0]] = parts[1] |
| 185 } |
| 186 } |
| 187 return result |
| 188 } |
| OLD | NEW |