go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/buildbucket/instrumented.go (about) 1 // Copyright 2022 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 buildbucket 16 17 import ( 18 "context" 19 20 "google.golang.org/genproto/googleapis/rpc/code" 21 "google.golang.org/grpc" 22 "google.golang.org/grpc/status" 23 24 bbpb "go.chromium.org/luci/buildbucket/proto" 25 "go.chromium.org/luci/common/clock" 26 "go.chromium.org/luci/common/errors" 27 "go.chromium.org/luci/common/logging" 28 29 "go.chromium.org/luci/cv/internal/metrics" 30 ) 31 32 func makeInstrumentedFactory(inner ClientFactory) ClientFactory { 33 return instrumentedFactory{ClientFactory: inner} 34 } 35 36 type instrumentedFactory struct { 37 ClientFactory 38 } 39 40 // MakeClient implements `ClientFactory`. 41 func (i instrumentedFactory) MakeClient(ctx context.Context, host, luciProject string) (Client, error) { 42 c, err := i.ClientFactory.MakeClient(ctx, host, luciProject) 43 if err != nil { 44 return nil, err 45 } 46 return instrumentedClient{ 47 luciProject: luciProject, 48 host: host, 49 inner: c, 50 }, nil 51 } 52 53 // instrumentedClient instruments Buildbucket RPCs. 54 type instrumentedClient struct { 55 luciProject string 56 host string 57 inner Client 58 } 59 60 // start records the start time and returns the func to report the end. 61 func (i instrumentedClient) start(ctx context.Context, method string) func(err error) error { 62 tStart := clock.Now(ctx) 63 return func(err error) error { 64 dur := clock.Since(ctx, tStart) 65 c := status.Code(errors.Unwrap(err)) 66 canonicalCode, ok := code.Code_name[int32(c)] 67 if !ok { 68 canonicalCode = c.String() // Code(%d) 69 } 70 metrics.Internal.BuildbucketRPCCount.Add(ctx, 1, i.luciProject, i.host, method, canonicalCode) 71 metrics.Internal.BuildbucketRPCDurations.Add(ctx, float64(dur.Milliseconds()), i.luciProject, i.host, method, canonicalCode) 72 73 return err 74 } 75 } 76 77 func (i instrumentedClient) GetBuild(ctx context.Context, in *bbpb.GetBuildRequest, opts ...grpc.CallOption) (*bbpb.Build, error) { 78 end := i.start(ctx, "GetBuild") 79 resp, err := i.inner.GetBuild(ctx, in, opts...) 80 return resp, end(err) 81 } 82 83 func (i instrumentedClient) SearchBuilds(ctx context.Context, in *bbpb.SearchBuildsRequest, opts ...grpc.CallOption) (*bbpb.SearchBuildsResponse, error) { 84 end := i.start(ctx, "SearchBuilds") 85 resp, err := i.inner.SearchBuilds(ctx, in, opts...) 86 return resp, end(err) 87 } 88 89 func (i instrumentedClient) CancelBuild(ctx context.Context, in *bbpb.CancelBuildRequest, opts ...grpc.CallOption) (*bbpb.Build, error) { 90 end := i.start(ctx, "CancelBuild") 91 resp, err := i.inner.CancelBuild(ctx, in, opts...) 92 return resp, end(err) 93 } 94 95 func (i instrumentedClient) Batch(ctx context.Context, in *bbpb.BatchRequest, opts ...grpc.CallOption) (*bbpb.BatchResponse, error) { 96 // LUCI CV doesn't mix different type of operations in the same batch request. 97 // It would only have multiple operations that are of the same type. 98 var method string 99 if len(in.GetRequests()) > 0 { // be defensive 100 req := in.GetRequests()[0] 101 switch req.GetRequest().(type) { 102 case *bbpb.BatchRequest_Request_CancelBuild: 103 method = "Batch.CancelBuild" 104 case *bbpb.BatchRequest_Request_GetBuild: 105 method = "Batch.GetBuild" 106 case *bbpb.BatchRequest_Request_SearchBuilds: 107 method = "Batch.SearchBuilds" 108 case *bbpb.BatchRequest_Request_ScheduleBuild: 109 method = "Batch.ScheduleBuild" 110 default: 111 panic(errors.Reason("unknown request type: %T", req)) 112 } 113 } else { 114 logging.Warningf(ctx, "calling buildbucket.Batch with empty requests") 115 } 116 end := i.start(ctx, method) 117 resp, err := i.inner.Batch(ctx, in, opts...) 118 return resp, end(err) 119 }