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  }