go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/internal/metrics/build_events_test.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package metrics 16 17 import ( 18 "context" 19 "testing" 20 "time" 21 22 "google.golang.org/protobuf/types/known/timestamppb" 23 24 "go.chromium.org/luci/common/clock/testclock" 25 "go.chromium.org/luci/common/tsmon" 26 "go.chromium.org/luci/common/tsmon/distribution" 27 28 "go.chromium.org/luci/buildbucket/appengine/model" 29 pb "go.chromium.org/luci/buildbucket/proto" 30 31 . "github.com/smartystreets/goconvey/convey" 32 ) 33 34 // lfv generate field values for legacy metrics 35 func lfv(vs ...any) []any { 36 ret := []any{"luci.project.bucket", "builder"} 37 return append(ret, vs...) 38 } 39 40 // fv generate field values for v2 metrics. 41 func fv(vs ...any) []any { 42 return vs 43 } 44 45 func pbTS(t time.Time) *timestamppb.Timestamp { 46 pbts := timestamppb.New(t) 47 return pbts 48 } 49 50 func TestBuildEvents(t *testing.T) { 51 t.Parallel() 52 53 Convey("Works", t, func() { 54 ctx, _ := tsmon.WithDummyInMemory(context.Background()) 55 ctx = WithServiceInfo(ctx, "svc", "job", "ins") 56 ctx = WithBuilder(ctx, "project", "bucket", "builder") 57 store := tsmon.Store(ctx) 58 59 b := &model.Build{ 60 ID: 1, 61 Proto: &pb.Build{ 62 Id: 1, 63 Builder: &pb.BuilderID{ 64 Project: "project", 65 Bucket: "bucket", 66 Builder: "builder", 67 }, 68 Canary: true, 69 }, 70 CreateTime: testclock.TestRecentTimeUTC, 71 } 72 73 Convey("buildCreated", func() { 74 b.Tags = []string{"os:linux"} 75 BuildCreated(ctx, b) 76 So(store.Get(ctx, V1.BuildCountCreated, time.Time{}, lfv("")), ShouldEqual, 1) 77 So(store.Get(ctx, V2.BuildCountCreated, time.Time{}, fv("None")), ShouldEqual, 1) 78 79 // user_agent 80 b.Tags = []string{"user_agent:gerrit"} 81 BuildCreated(ctx, b) 82 So(store.Get(ctx, V1.BuildCountCreated, time.Time{}, lfv("gerrit")), ShouldEqual, 1) 83 84 // experiments 85 b.Experiments = []string{"+exp1"} 86 BuildCreated(ctx, b) 87 So(store.Get(ctx, V2.BuildCountCreated, time.Time{}, fv("exp1")), ShouldEqual, 1) 88 }) 89 90 Convey("buildStarted", func() { 91 Convey("build/started", func() { 92 // canary 93 b.Proto.Canary = false 94 BuildStarted(ctx, b) 95 So(store.Get(ctx, V1.BuildCountStarted, time.Time{}, lfv(false)), ShouldEqual, 1) 96 97 b.Proto.Canary = true 98 BuildStarted(ctx, b) 99 So(store.Get(ctx, V1.BuildCountStarted, time.Time{}, lfv(true)), ShouldEqual, 1) 100 So(store.Get(ctx, V2.BuildCountStarted, time.Time{}, fv("None")), ShouldEqual, 2) 101 102 // experiments 103 b.Experiments = []string{"+exp1"} 104 BuildStarted(ctx, b) 105 So(store.Get(ctx, V2.BuildCountStarted, time.Time{}, fv("exp1")), ShouldEqual, 1) 106 }) 107 108 Convey("build/scheduling_durations", func() { 109 fields := lfv("", "", "", true) 110 b.Proto.StartTime = pbTS(b.CreateTime.Add(33 * time.Second)) 111 BuildStarted(ctx, b) 112 val := store.Get(ctx, V1.BuildDurationScheduling, time.Time{}, fields) 113 So(val.(*distribution.Distribution).Sum(), ShouldEqual, 33) 114 val = store.Get(ctx, V2.BuildDurationScheduling, time.Time{}, fv("None")) 115 So(val.(*distribution.Distribution).Sum(), ShouldEqual, 33) 116 117 // experiments 118 b.Experiments = []string{"+exp1"} 119 BuildStarted(ctx, b) 120 val = store.Get(ctx, V2.BuildDurationScheduling, time.Time{}, fv("exp1")) 121 So(val.(*distribution.Distribution).Sum(), ShouldEqual, 33) 122 }) 123 }) 124 125 Convey("BuildCompleted", func() { 126 Convey("builds/completed", func() { 127 b.Status = pb.Status_FAILURE 128 BuildCompleted(ctx, b) 129 v1fs := lfv(model.Failure.String(), model.BuildFailure.String(), "", true) 130 So(store.Get(ctx, V1.BuildCountCompleted, time.Time{}, v1fs), ShouldEqual, 1) 131 v2fs := fv("FAILURE", "None") 132 So(store.Get(ctx, V2.BuildCountCompleted, time.Time{}, v2fs), ShouldEqual, 1) 133 134 b.Status = pb.Status_CANCELED 135 BuildCompleted(ctx, b) 136 v1fs = lfv(model.Canceled.String(), "", model.ExplicitlyCanceled.String(), true) 137 So(store.Get(ctx, V1.BuildCountCompleted, time.Time{}, v1fs), ShouldEqual, 1) 138 v2fs[0] = "CANCELED" 139 So(store.Get(ctx, V2.BuildCountCompleted, time.Time{}, v2fs), ShouldEqual, 1) 140 141 b.Status = pb.Status_INFRA_FAILURE 142 BuildCompleted(ctx, b) 143 v1fs = lfv(model.Failure.String(), model.InfraFailure.String(), "", true) 144 So(store.Get(ctx, V1.BuildCountCompleted, time.Time{}, v1fs), ShouldEqual, 1) 145 v2fs[0] = "INFRA_FAILURE" 146 So(store.Get(ctx, V2.BuildCountCompleted, time.Time{}, v2fs), ShouldEqual, 1) 147 148 // timeout 149 b.Status = pb.Status_INFRA_FAILURE 150 b.Proto.StatusDetails = &pb.StatusDetails{Timeout: &pb.StatusDetails_Timeout{}} 151 BuildCompleted(ctx, b) 152 v1fs = lfv(model.Failure.String(), model.InfraFailure.String(), "", true) 153 So(store.Get(ctx, V1.BuildCountCompleted, time.Time{}, v1fs), ShouldEqual, 1) 154 v2fs = fv("INFRA_FAILURE", "None") 155 So(store.Get(ctx, V2.BuildCountCompleted, time.Time{}, v2fs), ShouldEqual, 2) 156 157 // experiments 158 b.Status = pb.Status_SUCCESS 159 b.Experiments = []string{"+exp1", "+exp2"} 160 v2fs = fv("SUCCESS", "exp1|exp2") 161 BuildCompleted(ctx, b) 162 So(store.Get(ctx, V2.BuildCountCompleted, time.Time{}, v2fs), ShouldEqual, 1) 163 }) 164 165 b.Status = pb.Status_SUCCESS 166 v1fs := lfv("SUCCESS", "", "", true) 167 v2fs := fv("SUCCESS", "None") 168 169 Convey("builds/cycle_durations", func() { 170 b.Proto.EndTime = pbTS(b.CreateTime.Add(33 * time.Second)) 171 BuildCompleted(ctx, b) 172 val := store.Get(ctx, V1.BuildDurationCycle, time.Time{}, v1fs) 173 So(val.(*distribution.Distribution).Sum(), ShouldEqual, 33) 174 val = store.Get(ctx, V2.BuildDurationCycle, time.Time{}, v2fs) 175 So(val.(*distribution.Distribution).Sum(), ShouldEqual, 33) 176 177 // experiments 178 b.Experiments = []string{"+exp2", "+exp1"} 179 BuildCompleted(ctx, b) 180 val = store.Get(ctx, V2.BuildDurationCycle, time.Time{}, fv("SUCCESS", "exp1|exp2")) 181 So(val.(*distribution.Distribution).Sum(), ShouldEqual, 33) 182 }) 183 184 Convey("builds/run_durations", func() { 185 b.Proto.StartTime = pbTS(b.CreateTime.Add(3 * time.Second)) 186 b.Proto.EndTime = pbTS(b.CreateTime.Add(33 * time.Second)) 187 188 BuildCompleted(ctx, b) 189 val := store.Get(ctx, V1.BuildDurationRun, time.Time{}, v1fs) 190 So(val.(*distribution.Distribution).Sum(), ShouldEqual, 30) 191 val = store.Get(ctx, V2.BuildDurationRun, time.Time{}, v2fs) 192 So(val.(*distribution.Distribution).Sum(), ShouldEqual, 30) 193 194 // experiments 195 b.Experiments = []string{"+exp2", "+exp1"} 196 BuildCompleted(ctx, b) 197 val = store.Get(ctx, V2.BuildDurationRun, time.Time{}, fv("SUCCESS", "exp1|exp2")) 198 So(val.(*distribution.Distribution).Sum(), ShouldEqual, 30) 199 }) 200 }) 201 202 Convey("ExpiredLeaseReset", func() { 203 b.Status = pb.Status_SCHEDULED 204 ExpiredLeaseReset(ctx, b) 205 So(store.Get(ctx, V1.ExpiredLeaseReset, time.Time{}, lfv("SCHEDULED")), ShouldEqual, 1) 206 }) 207 }) 208 }