blob: 896df9ff7936365d1bdd99fb4a74e386ca2d5e0f [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package metric is the API for defining metrics and updating their values.
//
// When you define a metric you must also define the names and types of any
// fields on that metric. Additionally, you can specify the target type of
// the metric. It is an error to define two metrics with the same and
// target type. It will cause a panic if there are duplicated metrics.
//
// When setting metric values, you must pass correct number of fields, with
// correct types, or the setter function will panic.
//
// Example:
// var (
// requests = metric.NewCounterWithTargetType("myapp/requests",
// "Number of requests",
// nil,
// field.String("status"),
// types.TaskType),
// )
// ...
// func handleRequest() {
// if success {
// requests.Add(1, "success")
// } else {
// requests.Add(1, "failure")
// }
// }
package metric
import (
"context"
"time"
"go.chromium.org/luci/common/tsmon"
"go.chromium.org/luci/common/tsmon/distribution"
"go.chromium.org/luci/common/tsmon/field"
"go.chromium.org/luci/common/tsmon/registry"
"go.chromium.org/luci/common/tsmon/target"
"go.chromium.org/luci/common/tsmon/types"
)
// Int is a non-cumulative integer gauge metric.
type Int interface {
types.Metric
Get(c context.Context, fieldVals ...interface{}) int64
Set(c context.Context, v int64, fieldVals ...interface{})
}
// Counter is a cumulative integer metric.
type Counter interface {
Int
Add(c context.Context, n int64, fieldVals ...interface{})
}
// Float is a non-cumulative floating-point gauge metric.
type Float interface {
types.Metric
Get(c context.Context, fieldVals ...interface{}) float64
Set(c context.Context, v float64, fieldVals ...interface{})
}
// FloatCounter is a cumulative floating-point metric.
type FloatCounter interface {
Float
Add(c context.Context, n float64, fieldVals ...interface{})
}
// String is a string-valued metric.
type String interface {
types.Metric
Get(c context.Context, fieldVals ...interface{}) string
Set(c context.Context, v string, fieldVals ...interface{})
}
// Bool is a boolean-valued metric.
type Bool interface {
types.Metric
Get(c context.Context, fieldVals ...interface{}) bool
Set(c context.Context, v bool, fieldVals ...interface{})
}
// NonCumulativeDistribution is a non-cumulative-distribution-valued metric.
type NonCumulativeDistribution interface {
types.DistributionMetric
Get(c context.Context, fieldVals ...interface{}) *distribution.Distribution
Set(c context.Context, d *distribution.Distribution, fieldVals ...interface{})
}
// CumulativeDistribution is a cumulative-distribution-valued metric.
type CumulativeDistribution interface {
NonCumulativeDistribution
Add(c context.Context, v float64, fieldVals ...interface{})
}
// NewInt returns a new non-cumulative integer gauge metric.
func NewInt(name string, description string, metadata *types.MetricMetadata, fields ...field.Field) Int {
return NewIntWithTargetType(name, target.NilType, description, metadata, fields...)
}
// NewIntWithTargetType returns a new non-cumulative integer gauge metric with a given TargetType.
func NewIntWithTargetType(name string, targetType types.TargetType, description string, metadata *types.MetricMetadata, fields ...field.Field) Int {
if metadata == nil {
metadata = &types.MetricMetadata{}
}
m := &intMetric{metric{
MetricInfo: types.MetricInfo{
Name: name,
Description: description,
Fields: fields,
ValueType: types.NonCumulativeIntType,
TargetType: targetType,
},
MetricMetadata: *metadata,
}}
registry.Add(m)
return m
}
// NewCounter returns a new cumulative integer metric.
func NewCounter(name string, description string, metadata *types.MetricMetadata, fields ...field.Field) Counter {
return NewCounterWithTargetType(name, target.NilType, description, metadata, fields...)
}
// NewCounterWithTargetType returns a new cumulative integer metric with a given TargetType.
func NewCounterWithTargetType(name string, targetType types.TargetType, description string, metadata *types.MetricMetadata, fields ...field.Field) Counter {
if metadata == nil {
metadata = &types.MetricMetadata{}
}
m := &counter{intMetric{metric{
MetricInfo: types.MetricInfo{
Name: name,
Description: description,
Fields: fields,
ValueType: types.CumulativeIntType,
TargetType: targetType,
},
MetricMetadata: *metadata,
}}}
registry.Add(m)
return m
}
// NewFloat returns a new non-cumulative floating-point gauge metric.
func NewFloat(name string, description string, metadata *types.MetricMetadata, fields ...field.Field) Float {
return NewFloatWithTargetType(name, target.NilType, description, metadata, fields...)
}
// NewFloatWithTargetType returns a new non-cumulative floating-point gauge metric with a given TargetType.
func NewFloatWithTargetType(name string, targetType types.TargetType, description string, metadata *types.MetricMetadata, fields ...field.Field) Float {
if metadata == nil {
metadata = &types.MetricMetadata{}
}
m := &floatMetric{metric{
MetricInfo: types.MetricInfo{
Name: name,
Description: description,
Fields: fields,
ValueType: types.NonCumulativeFloatType,
TargetType: targetType,
},
MetricMetadata: *metadata,
}}
registry.Add(m)
return m
}
// NewFloatCounter returns a new cumulative floating-point metric.
func NewFloatCounter(name string, description string, metadata *types.MetricMetadata, fields ...field.Field) FloatCounter {
return NewFloatCounterWithTargetType(name, target.NilType, description, metadata, fields...)
}
// NewFloatCounterWithTargetType returns a new cumulative floating-point metric with a given TargetType.
func NewFloatCounterWithTargetType(name string, targetType types.TargetType, description string, metadata *types.MetricMetadata, fields ...field.Field) FloatCounter {
if metadata == nil {
metadata = &types.MetricMetadata{}
}
m := &floatCounter{floatMetric{metric{
MetricInfo: types.MetricInfo{
Name: name,
Description: description,
Fields: fields,
ValueType: types.CumulativeFloatType,
TargetType: targetType,
},
MetricMetadata: *metadata,
}}}
registry.Add(m)
return m
}
// NewString returns a new string-valued metric.
func NewString(name string, description string, metadata *types.MetricMetadata, fields ...field.Field) String {
return NewStringWithTargetType(name, target.NilType, description, metadata, fields...)
}
// NewStringWithTargetType returns a new string-valued metric with a given TargetType
func NewStringWithTargetType(name string, targetType types.TargetType, description string, metadata *types.MetricMetadata, fields ...field.Field) String {
if metadata == nil {
metadata = &types.MetricMetadata{}
}
m := &stringMetric{metric{
MetricInfo: types.MetricInfo{
Name: name,
Description: description,
Fields: fields,
ValueType: types.StringType,
TargetType: targetType,
},
MetricMetadata: *metadata,
}}
registry.Add(m)
return m
}
// NewBool returns a new bool-valued metric.
func NewBool(name string, description string, metadata *types.MetricMetadata, fields ...field.Field) Bool {
return NewBoolWithTargetType(name, target.NilType, description, metadata, fields...)
}
// NewBoolWithTargetType returns a new bool-valued metric with a given TargetType.
func NewBoolWithTargetType(name string, targetType types.TargetType, description string, metadata *types.MetricMetadata, fields ...field.Field) Bool {
if metadata == nil {
metadata = &types.MetricMetadata{}
}
m := &boolMetric{metric{
MetricInfo: types.MetricInfo{
Name: name,
Description: description,
Fields: fields,
ValueType: types.BoolType,
TargetType: targetType,
},
MetricMetadata: *metadata,
}}
registry.Add(m)
return m
}
// NewCumulativeDistribution returns a new cumulative-distribution-valued
// metric.
func NewCumulativeDistribution(name string, description string, metadata *types.MetricMetadata, bucketer *distribution.Bucketer, fields ...field.Field) CumulativeDistribution {
return NewCumulativeDistributionWithTargetType(name, target.NilType, description, metadata, bucketer, fields...)
}
// NewCumulativeDistributionWithTargetType returns a new cumulative-distribution-valued
// metric with a given TargetType
func NewCumulativeDistributionWithTargetType(name string, targetType types.TargetType, description string, metadata *types.MetricMetadata, bucketer *distribution.Bucketer, fields ...field.Field) CumulativeDistribution {
if metadata == nil {
metadata = &types.MetricMetadata{}
}
m := &cumulativeDistributionMetric{
nonCumulativeDistributionMetric{
metric: metric{
MetricInfo: types.MetricInfo{
Name: name,
Description: description,
Fields: fields,
ValueType: types.CumulativeDistributionType,
TargetType: targetType,
},
MetricMetadata: *metadata,
},
bucketer: bucketer,
},
}
registry.Add(m)
return m
}
// NewNonCumulativeDistribution returns a new non-cumulative-distribution-valued
// metric.
func NewNonCumulativeDistribution(name string, description string, metadata *types.MetricMetadata, bucketer *distribution.Bucketer, fields ...field.Field) NonCumulativeDistribution {
return NewNonCumulativeDistributionWithTargetType(name, target.NilType, description, metadata, bucketer, fields...)
}
// NewNonCumulativeDistributionWithTargetType returns a new non-cumulative-distribution-valued
// metric with a given TargetType.
func NewNonCumulativeDistributionWithTargetType(name string, targetType types.TargetType, description string, metadata *types.MetricMetadata, bucketer *distribution.Bucketer, fields ...field.Field) NonCumulativeDistribution {
if metadata == nil {
metadata = &types.MetricMetadata{}
}
m := &nonCumulativeDistributionMetric{
metric: metric{
MetricInfo: types.MetricInfo{
Name: name,
Description: description,
Fields: fields,
ValueType: types.NonCumulativeDistributionType,
TargetType: targetType,
},
MetricMetadata: *metadata,
},
bucketer: bucketer,
}
registry.Add(m)
return m
}
// genericGet is a convenience function that tries to get a metric value from
// the store and returns the zero value if it didn't exist.
func (m *metric) genericGet(zero interface{}, c context.Context, fieldVals []interface{}) interface{} {
if ret := tsmon.Store(c).Get(c, m, m.fixedResetTime, fieldVals); ret != nil {
return ret
}
return zero
}
type metric struct {
types.MetricInfo
types.MetricMetadata
fixedResetTime time.Time
}
func (m *metric) Info() types.MetricInfo { return m.MetricInfo }
func (m *metric) Metadata() types.MetricMetadata { return m.MetricMetadata }
func (m *metric) SetFixedResetTime(t time.Time) { m.fixedResetTime = t }
type intMetric struct{ metric }
func (m *intMetric) Get(c context.Context, fieldVals ...interface{}) int64 {
return m.genericGet(int64(0), c, fieldVals).(int64)
}
func (m *intMetric) Set(c context.Context, v int64, fieldVals ...interface{}) {
tsmon.Store(c).Set(c, m, m.fixedResetTime, fieldVals, v)
}
type counter struct{ intMetric }
func (m *counter) Add(c context.Context, n int64, fieldVals ...interface{}) {
tsmon.Store(c).Incr(c, m, m.fixedResetTime, fieldVals, n)
}
type floatMetric struct{ metric }
func (m *floatMetric) Get(c context.Context, fieldVals ...interface{}) float64 {
return m.genericGet(float64(0.0), c, fieldVals).(float64)
}
func (m *floatMetric) Set(c context.Context, v float64, fieldVals ...interface{}) {
tsmon.Store(c).Set(c, m, m.fixedResetTime, fieldVals, v)
}
type floatCounter struct{ floatMetric }
func (m *floatCounter) Add(c context.Context, n float64, fieldVals ...interface{}) {
tsmon.Store(c).Incr(c, m, m.fixedResetTime, fieldVals, n)
}
type stringMetric struct{ metric }
func (m *stringMetric) Get(c context.Context, fieldVals ...interface{}) string {
return m.genericGet("", c, fieldVals).(string)
}
func (m *stringMetric) Set(c context.Context, v string, fieldVals ...interface{}) {
tsmon.Store(c).Set(c, m, m.fixedResetTime, fieldVals, v)
}
type boolMetric struct{ metric }
func (m *boolMetric) Get(c context.Context, fieldVals ...interface{}) bool {
return m.genericGet(false, c, fieldVals).(bool)
}
func (m *boolMetric) Set(c context.Context, v bool, fieldVals ...interface{}) {
tsmon.Store(c).Set(c, m, m.fixedResetTime, fieldVals, v)
}
type nonCumulativeDistributionMetric struct {
metric
bucketer *distribution.Bucketer
}
func (m *nonCumulativeDistributionMetric) Bucketer() *distribution.Bucketer { return m.bucketer }
func (m *nonCumulativeDistributionMetric) Get(c context.Context, fieldVals ...interface{}) *distribution.Distribution {
ret := m.genericGet((*distribution.Distribution)(nil), c, fieldVals)
return ret.(*distribution.Distribution)
}
func (m *nonCumulativeDistributionMetric) Set(c context.Context, v *distribution.Distribution, fieldVals ...interface{}) {
tsmon.Store(c).Set(c, m, m.fixedResetTime, fieldVals, v)
}
type cumulativeDistributionMetric struct {
nonCumulativeDistributionMetric
}
func (m *cumulativeDistributionMetric) Add(c context.Context, v float64, fieldVals ...interface{}) {
tsmon.Store(c).Incr(c, m, m.fixedResetTime, fieldVals, v)
}