| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package main | 5 package main |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "flag" | 8 "flag" |
| 9 "fmt" | 9 "fmt" |
| 10 "net/http" | 10 "net/http" |
| 11 "os" | 11 "os" |
| 12 "sort" |
| 12 "strings" | 13 "strings" |
| 13 "time" | 14 "time" |
| 14 | 15 |
| 15 "github.com/luci/luci-go/common/api/buildbucket/buildbucket/v1" | 16 "github.com/luci/luci-go/common/api/buildbucket/buildbucket/v1" |
| 16 ) | 17 ) |
| 17 | 18 |
| 18 var since = flag.Int64("since", 0, "analyze builds since this timestamp. Default
s to 10 days ago.") | 19 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 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 var builders = flag.String("builder", "", `comma-separated list of builder names
without swarming suffix, e.g. "Infra Presubmit"`) |
| 21 | 22 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 38 for { | 39 for { |
| 39 res, err := req.Do() | 40 res, err := req.Do() |
| 40 if err != nil { | 41 if err != nil { |
| 41 return result, err | 42 return result, err |
| 42 } | 43 } |
| 43 if res.Error != nil { | 44 if res.Error != nil { |
| 44 return result, fmt.Errorf(res.Error.Message) | 45 return result, fmt.Errorf(res.Error.Message) |
| 45 } | 46 } |
| 46 | 47 |
| 47 for _, b := range res.Builds { | 48 for _, b := range res.Builds { |
| 48 » » » createdAt := time.Unix(b.CreatedTs/1000000, 0) | 49 » » » if parseTimestamp(b.CreatedTs).Before(startingFrom) { |
| 49 » » » if createdAt.Before(startingFrom) { | |
| 50 return result, nil | 50 return result, nil |
| 51 } | 51 } |
| 52 result = append(result, b) | 52 result = append(result, b) |
| 53 } | 53 } |
| 54 | 54 |
| 55 if len(res.Builds) == 0 || res.NextCursor == "" { | 55 if len(res.Builds) == 0 || res.NextCursor == "" { |
| 56 break | 56 break |
| 57 } | 57 } |
| 58 req.StartCursor(res.NextCursor) | 58 req.StartCursor(res.NextCursor) |
| 59 } | 59 } |
| (...skipping 22 matching lines...) Expand all Loading... |
| 82 } | 82 } |
| 83 | 83 |
| 84 set.builds = append(set.builds, b) | 84 set.builds = append(set.builds, b) |
| 85 if set.bestResult == "" || b.Result == "SUCCESS" { | 85 if set.bestResult == "" || b.Result == "SUCCESS" { |
| 86 set.bestResult = b.Result | 86 set.bestResult = b.Result |
| 87 } | 87 } |
| 88 } | 88 } |
| 89 return results | 89 return results |
| 90 } | 90 } |
| 91 | 91 |
| 92 // medianTime returns median completed_time - created_time of successful builds. |
| 93 func medianTime(builds []*buildbucket.ApiBuildMessage) time.Duration { |
| 94 if len(builds) == 0 { |
| 95 return 0 |
| 96 } |
| 97 durations := make(durationSlice, 0, len(builds)) |
| 98 for _, b := range builds { |
| 99 if b.Result != "SUCCESS" { |
| 100 continue |
| 101 } |
| 102 created := parseTimestamp(b.CreatedTs) |
| 103 completed := parseTimestamp(b.CompletedTs) |
| 104 durations = append(durations, completed.Sub(created)) |
| 105 } |
| 106 sort.Sort(durations) |
| 107 return durations[len(durations)/2] |
| 108 } |
| 109 |
| 92 func run() error { | 110 func run() error { |
| 93 flag.Parse() | 111 flag.Parse() |
| 94 if *bucket == "" { | 112 if *bucket == "" { |
| 95 return fmt.Errorf("bucket is not specified") | 113 return fmt.Errorf("bucket is not specified") |
| 96 } | 114 } |
| 97 if *builders == "" { | 115 if *builders == "" { |
| 98 return fmt.Errorf("builders are not specified") | 116 return fmt.Errorf("builders are not specified") |
| 99 } | 117 } |
| 100 if len(flag.Args()) > 0 { | 118 if len(flag.Args()) > 0 { |
| 101 return fmt.Errorf("unexpected arguments: %s", flag.Args()) | 119 return fmt.Errorf("unexpected arguments: %s", flag.Args()) |
| (...skipping 12 matching lines...) Expand all Loading... |
| 114 for i, builder := range strings.Split(*builders, ",") { | 132 for i, builder := range strings.Split(*builders, ",") { |
| 115 builder = strings.TrimSpace(builder) | 133 builder = strings.TrimSpace(builder) |
| 116 if builder == "" { | 134 if builder == "" { |
| 117 continue | 135 continue |
| 118 } | 136 } |
| 119 | 137 |
| 120 if i > 0 { | 138 if i > 0 { |
| 121 fmt.Println() | 139 fmt.Println() |
| 122 } | 140 } |
| 123 fmt.Printf("builder %q\n", builder) | 141 fmt.Printf("builder %q\n", builder) |
| 124 » » fmt.Printf("searching for all builds since timestamp %d till %d.
..\n", | 142 » » if err := compareBuilder(builder, startingFrom); err != nil { |
| 125 » » » startingFrom.Unix(), time.Now().Unix()) | 143 » » » return err |
| 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 } | 144 } |
| 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 } | 145 } |
| 169 return nil | 146 return nil |
| 170 } | 147 } |
| 171 | 148 |
| 149 func compareBuilder(builder string, startingFrom time.Time) error { |
| 150 fmt.Printf("searching for all builds since timestamp %d till %d...\n", |
| 151 startingFrom.Unix(), time.Now().Unix()) |
| 152 // We will actually fetch builds after after time.Now too, but it is fin
e. |
| 153 swarmingBuilds, err := fetchBuilds(*bucket, builder+swarmingSuffix, star
tingFrom) |
| 154 if err != nil { |
| 155 return fmt.Errorf("could not fetch builds: %s", err) |
| 156 } |
| 157 if len(swarmingBuilds) == 0 { |
| 158 fmt.Printf("no swarming builds for builder %q\n", builder) |
| 159 return nil |
| 160 } |
| 161 buildbotBuilds, err := fetchBuilds(*bucket, builder, startingFrom) |
| 162 if err != nil { |
| 163 return fmt.Errorf("could not fetch builds: %s", err) |
| 164 } |
| 165 if len(buildbotBuilds) == 0 { |
| 166 fmt.Printf("no buildbot builds for builder %q\n", builder) |
| 167 return nil |
| 168 } |
| 169 |
| 170 swarmingBuildSets := groupBuilds(swarmingBuilds) |
| 171 buildbotBuildSets := groupBuilds(buildbotBuilds) |
| 172 |
| 173 consistentN := 0 |
| 174 inconsistentN := 0 |
| 175 for setName, swarmingSet := range swarmingBuildSets { |
| 176 buildbotSet := buildbotBuildSets[setName] |
| 177 if buildbotSet == nil { |
| 178 fmt.Printf("no buildbot builds for buildset %s\n", setNa
me) |
| 179 continue |
| 180 } |
| 181 if buildbotSet.bestResult == swarmingSet.bestResult { |
| 182 consistentN++ |
| 183 continue |
| 184 } |
| 185 inconsistentN++ |
| 186 |
| 187 fmt.Printf("%s is inconsistent\n", setName) |
| 188 for _, b := range swarmingSet.builds { |
| 189 fmt.Printf(" %s %s\n", b.Result, b.Url) |
| 190 } |
| 191 for _, b := range buildbotSet.builds { |
| 192 fmt.Printf(" %s %s\n", b.Result, b.Url) |
| 193 } |
| 194 } |
| 195 |
| 196 fmt.Printf("%0.2f%% consistent build sets, %d buildbot builds, %d swarmi
ng builds\n", |
| 197 100*float64(consistentN)/float64(consistentN+inconsistentN), len
(buildbotBuilds), len(swarmingBuilds)) |
| 198 |
| 199 swarmingTime := medianTime(swarmingBuilds) |
| 200 buildbotTime := medianTime(buildbotBuilds) |
| 201 factor := float64(buildbotTime) / float64(swarmingTime) |
| 202 if factor >= 1 { |
| 203 fmt.Printf("swarming is %.1fx faster\n", factor) |
| 204 } else { |
| 205 fmt.Printf("swarming is %.1fx slower\n", 1/factor) |
| 206 } |
| 207 fmt.Printf("median times: buildbot %s, swarming %s\n", buildbotTime, swa
rmingTime) |
| 208 |
| 209 return nil |
| 210 } |
| 211 |
| 172 func main() { | 212 func main() { |
| 173 if err := run(); err != nil { | 213 if err := run(); err != nil { |
| 174 fmt.Fprintln(os.Stderr, err) | 214 fmt.Fprintln(os.Stderr, err) |
| 175 os.Exit(1) | 215 os.Exit(1) |
| 176 } | 216 } |
| 177 } | 217 } |
| 178 | 218 |
| 179 func parseTags(tags []string) map[string]string { | 219 func parseTags(tags []string) map[string]string { |
| 180 result := make(map[string]string, len(tags)) | 220 result := make(map[string]string, len(tags)) |
| 181 for _, t := range tags { | 221 for _, t := range tags { |
| 182 parts := strings.SplitN(t, ":", 2) | 222 parts := strings.SplitN(t, ":", 2) |
| 183 if len(parts) == 2 { | 223 if len(parts) == 2 { |
| 184 result[parts[0]] = parts[1] | 224 result[parts[0]] = parts[1] |
| 185 } | 225 } |
| 186 } | 226 } |
| 187 return result | 227 return result |
| 188 } | 228 } |
| 229 |
| 230 func parseTimestamp(ts int64) time.Time { |
| 231 if ts == 0 { |
| 232 return time.Time{} |
| 233 } |
| 234 return time.Unix(ts/1000000, 0) |
| 235 } |
| 236 |
| 237 type durationSlice []time.Duration |
| 238 |
| 239 func (a durationSlice) Len() int { return len(a) } |
| 240 func (a durationSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
| 241 func (a durationSlice) Less(i, j int) bool { return a[i] < a[j] } |
| OLD | NEW |