blob: 42acaae56ee0ba376b2e75e97e1375ff1ca642df [file] [log] [blame]
// Copyright 2019, OpenCensus 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
import (
"sync"
"time"
"go.opencensus.io/internal/tagencoding"
"go.opencensus.io/metric/metricdata"
)
// baseMetric is common representation for gauge and cumulative metrics.
//
// baseMetric maintains a value for each combination of label values passed to
// Set, Add, or Inc method.
//
// baseMetric should not be used directly, use metric specific type such as
// Float64Gauge or Int64Gauge.
type baseMetric struct {
vals sync.Map
desc metricdata.Descriptor
start time.Time
keys []metricdata.LabelKey
constLabelValues []metricdata.LabelValue
bmType baseMetricType
}
type baseMetricType int
const (
gaugeInt64 baseMetricType = iota
gaugeFloat64
derivedGaugeInt64
derivedGaugeFloat64
cumulativeInt64
cumulativeFloat64
derivedCumulativeInt64
derivedCumulativeFloat64
)
type baseEntry interface {
read(t time.Time) metricdata.Point
}
func (bm *baseMetric) startTime() *time.Time {
switch bm.bmType {
case cumulativeInt64, cumulativeFloat64, derivedCumulativeInt64, derivedCumulativeFloat64:
return &bm.start
default:
// gauges don't have start time.
return nil
}
}
// Read returns the current values of the baseMetric as a metric for export.
func (bm *baseMetric) read() *metricdata.Metric {
now := time.Now()
startTime := bm.startTime()
if startTime == nil {
startTime = &now
}
m := &metricdata.Metric{
Descriptor: bm.desc,
}
bm.vals.Range(func(k, v interface{}) bool {
entry := v.(baseEntry)
key := k.(string)
labelVals := bm.decodeLabelVals(key)
m.TimeSeries = append(m.TimeSeries, &metricdata.TimeSeries{
StartTime: *startTime,
LabelValues: labelVals,
Points: []metricdata.Point{
entry.read(now),
},
})
return true
})
return m
}
func (bm *baseMetric) encodeLabelVals(labelVals []metricdata.LabelValue) string {
vb := &tagencoding.Values{}
for _, v := range labelVals {
b := make([]byte, 1, len(v.Value)+1)
if v.Present {
b[0] = 1
b = append(b, []byte(v.Value)...)
}
vb.WriteValue(b)
}
return string(vb.Bytes())
}
func (bm *baseMetric) decodeLabelVals(s string) []metricdata.LabelValue {
vals := make([]metricdata.LabelValue, 0, len(bm.keys))
vb := &tagencoding.Values{Buffer: []byte(s)}
for range bm.keys {
v := vb.ReadValue()
if v[0] == 0 {
vals = append(vals, metricdata.LabelValue{})
} else {
vals = append(vals, metricdata.NewLabelValue(string(v[1:])))
}
}
return vals
}
func (bm *baseMetric) entryForValues(labelVals []metricdata.LabelValue, newEntry func() baseEntry) (interface{}, error) {
labelVals = append(bm.constLabelValues, labelVals...)
if len(labelVals) != len(bm.keys) {
return nil, errKeyValueMismatch
}
mapKey := bm.encodeLabelVals(labelVals)
if entry, ok := bm.vals.Load(mapKey); ok {
return entry, nil
}
entry, _ := bm.vals.LoadOrStore(mapKey, newEntry())
return entry, nil
}
func (bm *baseMetric) upsertEntry(labelVals []metricdata.LabelValue, newEntry func() baseEntry) error {
labelVals = append(bm.constLabelValues, labelVals...)
if len(labelVals) != len(bm.keys) {
return errKeyValueMismatch
}
mapKey := bm.encodeLabelVals(labelVals)
bm.vals.Delete(mapKey)
bm.vals.Store(mapKey, newEntry())
return nil
}