go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/internal/metrics/v1.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  	"fmt"
    19  	"math"
    20  
    21  	"go.chromium.org/luci/buildbucket/appengine/model"
    22  	pb "go.chromium.org/luci/buildbucket/proto"
    23  	"go.chromium.org/luci/common/tsmon/distribution"
    24  	"go.chromium.org/luci/common/tsmon/field"
    25  	"go.chromium.org/luci/common/tsmon/metric"
    26  	"go.chromium.org/luci/common/tsmon/types"
    27  )
    28  
    29  var (
    30  	// A common set of field definitions for build metrics.
    31  	fieldDefs = map[string]field.Field{
    32  		"bucket":               field.String("bucket"),
    33  		"builder":              field.String("builder"),
    34  		"canary":               field.Bool("canary"),
    35  		"cancelation_reason":   field.String("cancelation_reason"),
    36  		"failure_reason":       field.String("failure_reason"),
    37  		"must_be_never_leased": field.Bool("must_be_never_leased"),
    38  		"result":               field.String("result"),
    39  		"status":               field.String("status"),
    40  		"user_agent":           field.String("user_agent"),
    41  	}
    42  
    43  	// V1 is a collection of metric objects for V1 metrics.
    44  	V1 = struct {
    45  		BuildCount              metric.Int
    46  		BuildCountCreated       metric.Counter
    47  		BuildCountStarted       metric.Counter
    48  		BuildCountCompleted     metric.Counter
    49  		BuildDurationCycle      metric.CumulativeDistribution
    50  		BuildDurationRun        metric.CumulativeDistribution
    51  		BuildDurationScheduling metric.CumulativeDistribution
    52  		ExpiredLeaseReset       metric.Counter
    53  		MaxAgeScheduled         metric.Float
    54  	}{
    55  		BuildCount: metric.NewInt(
    56  			"buildbucket/builds/count",
    57  			"Number of pending/running prod builds", nil,
    58  			bFields("status")...,
    59  		),
    60  		BuildCountCreated: metric.NewCounter(
    61  			"buildbucket/builds/created",
    62  			"Build creation", nil,
    63  			bFields("user_agent")...,
    64  		),
    65  		BuildCountStarted: metric.NewCounter(
    66  			"buildbucket/builds/started",
    67  			"Build start", nil,
    68  			bFields("canary")...,
    69  		),
    70  		BuildCountCompleted: metric.NewCounter(
    71  			"buildbucket/builds/completed",
    72  			"Build completion, including success, failure and cancellation", nil,
    73  			bFields("result", "failure_reason", "cancelation_reason", "canary")...,
    74  		),
    75  		BuildDurationCycle: newbuildDurationMetric(
    76  			"buildbucket/builds/cycle_durations",
    77  			"Duration between build creation and completion",
    78  		),
    79  		BuildDurationRun: newbuildDurationMetric(
    80  			"buildbucket/builds/run_durations",
    81  			"Duration between build start and completion",
    82  		),
    83  		BuildDurationScheduling: newbuildDurationMetric(
    84  			"buildbucket/builds/scheduling_durations",
    85  			"Duration between build creation and start",
    86  		),
    87  		ExpiredLeaseReset: metric.NewCounter(
    88  			"buildbucket/builds/lease_expired",
    89  			"Build lease expirations", nil,
    90  			bFields("status")...,
    91  		),
    92  		MaxAgeScheduled: metric.NewFloat(
    93  			"buildbucket/builds/max_age_scheduled",
    94  			"Age of the oldest SCHEDULED build",
    95  			&types.MetricMetadata{Units: types.Seconds},
    96  			bFields("must_be_never_leased")...,
    97  		),
    98  	}
    99  )
   100  
   101  func bFields(extraFields ...string) []field.Field {
   102  	fs := make([]field.Field, 2+len(extraFields))
   103  	fs[0], fs[1] = fieldDefs["bucket"], fieldDefs["builder"]
   104  	for i, n := range extraFields {
   105  		f, ok := fieldDefs[n]
   106  		if !ok {
   107  			panic(fmt.Sprintf("unknown build field %q", n))
   108  		}
   109  		fs[i+2] = f
   110  	}
   111  	return fs
   112  }
   113  
   114  func newbuildDurationMetric(name, description string, extraFields ...string) metric.CumulativeDistribution {
   115  	fs := []string{"result", "failure_reason", "cancelation_reason", "canary"}
   116  	return metric.NewCumulativeDistribution(
   117  		name, description, &types.MetricMetadata{Units: types.Seconds},
   118  		// Bucketer for 1s..48h range
   119  		distribution.GeometricBucketer(math.Pow(10, 0.053), 100),
   120  		bFields(append(fs, extraFields...)...)...,
   121  	)
   122  }
   123  
   124  func getLegacyMetricFields(b *model.Build) (legacyStatus, result, failureR, cancelationR string) {
   125  	// The default values are "" instead of UNSET for backwards compatibility.
   126  	switch b.Status {
   127  	case pb.Status_SCHEDULED:
   128  		legacyStatus = model.Scheduled.String()
   129  	case pb.Status_STARTED:
   130  		legacyStatus = model.Started.String()
   131  	case pb.Status_SUCCESS:
   132  		legacyStatus = model.Completed.String()
   133  		result = model.Success.String()
   134  	case pb.Status_FAILURE:
   135  		legacyStatus = model.Completed.String()
   136  		result = model.Failure.String()
   137  		failureR = model.BuildFailure.String()
   138  	case pb.Status_INFRA_FAILURE:
   139  		legacyStatus = model.Completed.String()
   140  		if b.Proto.StatusDetails.GetTimeout() != nil {
   141  			result = model.Canceled.String()
   142  			cancelationR = model.TimeoutCanceled.String()
   143  		} else {
   144  			result = model.Failure.String()
   145  			failureR = model.InfraFailure.String()
   146  		}
   147  	case pb.Status_CANCELED:
   148  		legacyStatus = model.Completed.String()
   149  		result = model.Canceled.String()
   150  		cancelationR = model.ExplicitlyCanceled.String()
   151  	default:
   152  		panic(fmt.Sprintf("getLegacyMetricFields: invalid status %q", b.Status))
   153  	}
   154  	return
   155  }
   156  
   157  // legacyBucketName returns the V1 luci bucket name.
   158  // e.g., "luci.chromium.try".
   159  func legacyBucketName(project, bucket string) string {
   160  	return fmt.Sprintf("luci.%s.%s", project, bucket)
   161  }