From 7a33b99ccaf762432a58946cc633a0479acdb8bf Mon Sep 17 00:00:00 2001 From: rghetia Date: Wed, 3 Apr 2019 10:05:34 -0700 Subject: [PATCH] Refactor gauge and registry to accommodate cumulative. (#1089) * Refactor gauge and registry to accomodate cummulative. - use common baseMetric type to manage gauge and cumulative. * fix copyright and renamed couple of func. --- metric/common.go | 126 ++++++++++++++++++++++++++++++++++++++++++ metric/doc.go | 2 +- metric/error_const.go | 6 +- metric/gauge.go | 108 +++--------------------------------- metric/gauge_test.go | 28 +++++----- metric/registry.go | 73 +++++++++++------------- 6 files changed, 184 insertions(+), 159 deletions(-) create mode 100644 metric/common.go diff --git a/metric/common.go b/metric/common.go new file mode 100644 index 000000000..3dcaf1971 --- /dev/null +++ b/metric/common.go @@ -0,0 +1,126 @@ +// 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 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 []string + 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 +} + +// Read returns the current values of the baseMetric as a metric for export. +func (bm *baseMetric) read() *metricdata.Metric { + now := time.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: now, // Gauge value is instantaneous. + 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) { + 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 { + if len(labelVals) != len(bm.keys) { + return errKeyValueMismatch + } + mapKey := bm.encodeLabelVals(labelVals) + bm.vals.Delete(mapKey) + bm.vals.Store(mapKey, newEntry()) + return nil +} diff --git a/metric/doc.go b/metric/doc.go index 485ee8f58..4b69f9aaa 100644 --- a/metric/doc.go +++ b/metric/doc.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package metric support for gauge metrics. +// Package metric support for gauge and cumulative metrics. // // This is an EXPERIMENTAL package, and may change in arbitrary ways without // notice. diff --git a/metric/error_const.go b/metric/error_const.go index c2bdf2b59..da1589419 100644 --- a/metric/error_const.go +++ b/metric/error_const.go @@ -17,7 +17,7 @@ package metric import "errors" var ( - errInvalidParam = errors.New("invalid parameter") - errGaugeExistsWithDiffType = errors.New("gauge with same name exists with a different type") - errKeyValueMismatch = errors.New("must supply the same number of label values as keys used to construct this gauge") + errInvalidParam = errors.New("invalid parameter") + errMetricExistsWithDiffType = errors.New("metric with same name exists with a different type") + errKeyValueMismatch = errors.New("must supply the same number of label values as keys used to construct this metric") ) diff --git a/metric/gauge.go b/metric/gauge.go index 0f5dcba14..d8104844f 100644 --- a/metric/gauge.go +++ b/metric/gauge.go @@ -16,110 +16,18 @@ package metric import ( "math" - "sync" "sync/atomic" "time" - "go.opencensus.io/internal/tagencoding" "go.opencensus.io/metric/metricdata" ) -// gauge represents a quantity that can go up an down, for example queue depth -// or number of outstanding requests. -// -// gauge maintains a value for each combination of of label values passed to -// the Set or Add methods. -// -// gauge should not be used directly, use Float64Gauge or Int64Gauge. -type gauge struct { - vals sync.Map - desc metricdata.Descriptor - start time.Time - keys []string - gType gaugeType -} - -type gaugeEntry interface { - read(t time.Time) metricdata.Point -} - -// Read returns the current values of the gauge as a metric for export. -func (g *gauge) read() *metricdata.Metric { - now := time.Now() - m := &metricdata.Metric{ - Descriptor: g.desc, - } - g.vals.Range(func(k, v interface{}) bool { - entry := v.(gaugeEntry) - key := k.(string) - labelVals := g.labelValues(key) - m.TimeSeries = append(m.TimeSeries, &metricdata.TimeSeries{ - StartTime: now, // Gauge value is instantaneous. - LabelValues: labelVals, - Points: []metricdata.Point{ - entry.read(now), - }, - }) - return true - }) - return m -} - -func (g *gauge) mapKey(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 (g *gauge) labelValues(s string) []metricdata.LabelValue { - vals := make([]metricdata.LabelValue, 0, len(g.keys)) - vb := &tagencoding.Values{Buffer: []byte(s)} - for range g.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 (g *gauge) entryForValues(labelVals []metricdata.LabelValue, newEntry func() gaugeEntry) (interface{}, error) { - if len(labelVals) != len(g.keys) { - return nil, errKeyValueMismatch - } - mapKey := g.mapKey(labelVals) - if entry, ok := g.vals.Load(mapKey); ok { - return entry, nil - } - entry, _ := g.vals.LoadOrStore(mapKey, newEntry()) - return entry, nil -} - -func (g *gauge) upsertEntry(labelVals []metricdata.LabelValue, newEntry func() gaugeEntry) error { - if len(labelVals) != len(g.keys) { - return errKeyValueMismatch - } - mapKey := g.mapKey(labelVals) - g.vals.Delete(mapKey) - g.vals.Store(mapKey, newEntry()) - return nil -} - // Float64Gauge represents a float64 value that can go up and down. // // Float64Gauge maintains a float64 value for each combination of of label values // passed to the Set or Add methods. type Float64Gauge struct { - g gauge + bm baseMetric } // Float64Entry represents a single value of the gauge corresponding to a set @@ -142,7 +50,7 @@ func (e *Float64Entry) read(t time.Time) metricdata.Point { // The number of label values supplied must be exactly the same as the number // of keys supplied when this gauge was created. func (g *Float64Gauge) GetEntry(labelVals ...metricdata.LabelValue) (*Float64Entry, error) { - entry, err := g.g.entryForValues(labelVals, func() gaugeEntry { + entry, err := g.bm.entryForValues(labelVals, func() baseEntry { return &Float64Entry{} }) if err != nil { @@ -171,7 +79,7 @@ func (e *Float64Entry) Add(val float64) { // Int64Gauge maintains an int64 value for each combination of label values passed to the // Set or Add methods. type Int64Gauge struct { - g gauge + bm baseMetric } // Int64GaugeEntry represents a single value of the gauge corresponding to a set @@ -194,7 +102,7 @@ func (e *Int64GaugeEntry) read(t time.Time) metricdata.Point { // The number of label values supplied must be exactly the same as the number // of keys supplied when this gauge was created. func (g *Int64Gauge) GetEntry(labelVals ...metricdata.LabelValue) (*Int64GaugeEntry, error) { - entry, err := g.g.entryForValues(labelVals, func() gaugeEntry { + entry, err := g.bm.entryForValues(labelVals, func() baseEntry { return &Int64GaugeEntry{} }) if err != nil { @@ -219,7 +127,7 @@ func (e *Int64GaugeEntry) Add(val int64) { // These objects implement Int64DerivedGaugeInterface to read instantaneous value // representing the object. type Int64DerivedGauge struct { - g gauge + bm baseMetric } type int64DerivedGaugeEntry struct { @@ -241,7 +149,7 @@ func (g *Int64DerivedGauge) UpsertEntry(fn func() int64, labelVals ...metricdata if fn == nil { return errInvalidParam } - return g.g.upsertEntry(labelVals, func() gaugeEntry { + return g.bm.upsertEntry(labelVals, func() baseEntry { return &int64DerivedGaugeEntry{fn} }) } @@ -252,7 +160,7 @@ func (g *Int64DerivedGauge) UpsertEntry(fn func() int64, labelVals ...metricdata // These objects implement Float64DerivedGaugeInterface to read instantaneous value // representing the object. type Float64DerivedGauge struct { - g gauge + bm baseMetric } type float64DerivedGaugeEntry struct { @@ -274,7 +182,7 @@ func (g *Float64DerivedGauge) UpsertEntry(fn func() float64, labelVals ...metric if fn == nil { return errInvalidParam } - return g.g.upsertEntry(labelVals, func() gaugeEntry { + return g.bm.upsertEntry(labelVals, func() baseEntry { return &float64DerivedGaugeEntry{fn} }) } diff --git a/metric/gauge_test.go b/metric/gauge_test.go index 8ed358b1e..b8c41d14d 100644 --- a/metric/gauge_test.go +++ b/metric/gauge_test.go @@ -86,13 +86,13 @@ func TestGaugeMetricDescriptor(t *testing.T) { r := NewRegistry() gf, _ := r.AddFloat64Gauge("float64_gauge") - compareType(gf.g.desc.Type, metricdata.TypeGaugeFloat64, t) + compareType(gf.bm.desc.Type, metricdata.TypeGaugeFloat64, t) gi, _ := r.AddInt64Gauge("int64_gauge") - compareType(gi.g.desc.Type, metricdata.TypeGaugeInt64, t) + compareType(gi.bm.desc.Type, metricdata.TypeGaugeInt64, t) dgf, _ := r.AddFloat64DerivedGauge("derived_float64_gauge") - compareType(dgf.g.desc.Type, metricdata.TypeGaugeFloat64, t) + compareType(dgf.bm.desc.Type, metricdata.TypeGaugeFloat64, t) dgi, _ := r.AddInt64DerivedGauge("derived_int64_gauge") - compareType(dgi.g.desc.Type, metricdata.TypeGaugeInt64, t) + compareType(dgi.bm.desc.Type, metricdata.TypeGaugeInt64, t) } func compareType(got, want metricdata.Type, t *testing.T) { @@ -110,7 +110,7 @@ func TestGaugeMetricOptionDesc(t *testing.T) { Description: "test", Type: metricdata.TypeGaugeFloat64, } - got := gf.g.desc + got := gf.bm.desc if !cmp.Equal(got, want) { t.Errorf("metric option description: got %v, want %v\n", got, want) } @@ -125,7 +125,7 @@ func TestGaugeMetricOptionUnit(t *testing.T) { Unit: metricdata.UnitMilliseconds, Type: metricdata.TypeGaugeFloat64, } - got := gf.g.desc + got := gf.bm.desc if !cmp.Equal(got, want) { t.Errorf("metric descriptor: got %v, want %v\n", got, want) } @@ -140,7 +140,7 @@ func TestGaugeMetricOptionLabelKeys(t *testing.T) { LabelKeys: []string{"k1", "k3"}, Type: metricdata.TypeGaugeFloat64, } - got := gf.g.desc + got := gf.bm.desc if !cmp.Equal(got, want) { t.Errorf("metric descriptor: got %v, want %v\n", got, want) } @@ -154,7 +154,7 @@ func TestGaugeMetricOptionDefault(t *testing.T) { Name: name, Type: metricdata.TypeGaugeFloat64, } - got := gf.g.desc + got := gf.bm.desc if !cmp.Equal(got, want) { t.Errorf("metric descriptor: got %v, want %v\n", got, want) } @@ -227,15 +227,15 @@ func TestGaugeWithSameNameDiffType(t *testing.T) { r.AddInt64Gauge("g") _, gotErr := r.AddFloat64Gauge("g") if gotErr == nil { - t.Errorf("got: nil, want error: %v", errGaugeExistsWithDiffType) + t.Errorf("got: nil, want error: %v", errMetricExistsWithDiffType) } _, gotErr = r.AddInt64DerivedGauge("g") if gotErr == nil { - t.Errorf("got: nil, want error: %v", errGaugeExistsWithDiffType) + t.Errorf("got: nil, want error: %v", errMetricExistsWithDiffType) } _, gotErr = r.AddFloat64DerivedGauge("g") if gotErr == nil { - t.Errorf("got: nil, want error: %v", errGaugeExistsWithDiffType) + t.Errorf("got: nil, want error: %v", errMetricExistsWithDiffType) } } @@ -262,11 +262,11 @@ func TestMapKey(t *testing.T) { } for i, tc := range cases { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { - g := &gauge{ + g := &baseMetric{ keys: make([]string, len(tc)), } - mk := g.mapKey(tc) - vals := g.labelValues(mk) + mk := g.encodeLabelVals(tc) + vals := g.decodeLabelVals(mk) if diff := cmp.Diff(vals, tc); diff != "" { t.Errorf("values differ after serialization -got +want: %s", diff) } diff --git a/metric/registry.go b/metric/registry.go index 80df54229..6b1ff323f 100644 --- a/metric/registry.go +++ b/metric/registry.go @@ -21,22 +21,13 @@ import ( "go.opencensus.io/metric/metricdata" ) -// Registry creates and manages a set of gauges. -// External synchronization is required if you want to add gauges to the same +// Registry creates and manages a set of gauges and cumulative. +// External synchronization is required if you want to add gauges and cumulative to the same // registry from multiple goroutines. type Registry struct { - gauges sync.Map + baseMetrics sync.Map } -type gaugeType int - -const ( - gaugeInt64 gaugeType = iota - gaugeFloat64 - derivedGaugeInt64 - derivedGaugeFloat64 -) - //TODO: [rghetia] add constant labels. type metricOptions struct { unit metricdata.Unit @@ -76,11 +67,11 @@ func NewRegistry() *Registry { // AddFloat64Gauge creates and adds a new float64-valued gauge to this registry. func (r *Registry) AddFloat64Gauge(name string, mos ...Options) (*Float64Gauge, error) { f := &Float64Gauge{ - g: gauge{ - gType: gaugeFloat64, + bm: baseMetric{ + bmType: gaugeFloat64, }, } - _, err := r.initGauge(&f.g, name, mos...) + _, err := r.initBaseMetric(&f.bm, name, mos...) if err != nil { return nil, err } @@ -90,11 +81,11 @@ func (r *Registry) AddFloat64Gauge(name string, mos ...Options) (*Float64Gauge, // AddInt64Gauge creates and adds a new int64-valued gauge to this registry. func (r *Registry) AddInt64Gauge(name string, mos ...Options) (*Int64Gauge, error) { i := &Int64Gauge{ - g: gauge{ - gType: gaugeInt64, + bm: baseMetric{ + bmType: gaugeInt64, }, } - _, err := r.initGauge(&i.g, name, mos...) + _, err := r.initBaseMetric(&i.bm, name, mos...) if err != nil { return nil, err } @@ -106,11 +97,11 @@ func (r *Registry) AddInt64Gauge(name string, mos ...Options) (*Int64Gauge, erro // provides its value by implementing func() int64. func (r *Registry) AddInt64DerivedGauge(name string, mos ...Options) (*Int64DerivedGauge, error) { i := &Int64DerivedGauge{ - g: gauge{ - gType: derivedGaugeInt64, + bm: baseMetric{ + bmType: derivedGaugeInt64, }, } - _, err := r.initGauge(&i.g, name, mos...) + _, err := r.initBaseMetric(&i.bm, name, mos...) if err != nil { return nil, err } @@ -122,19 +113,19 @@ func (r *Registry) AddInt64DerivedGauge(name string, mos ...Options) (*Int64Deri // provides its value by implementing func() float64. func (r *Registry) AddFloat64DerivedGauge(name string, mos ...Options) (*Float64DerivedGauge, error) { f := &Float64DerivedGauge{ - g: gauge{ - gType: derivedGaugeFloat64, + bm: baseMetric{ + bmType: derivedGaugeFloat64, }, } - _, err := r.initGauge(&f.g, name, mos...) + _, err := r.initBaseMetric(&f.bm, name, mos...) if err != nil { return nil, err } return f, nil } -func gTypeToMetricType(g *gauge) metricdata.Type { - switch g.gType { +func bmTypeToMetricType(bm *baseMetric) metricdata.Type { + switch bm.bmType { case derivedGaugeFloat64: return metricdata.TypeGaugeFloat64 case derivedGaugeInt64: @@ -144,7 +135,7 @@ func gTypeToMetricType(g *gauge) metricdata.Type { case gaugeInt64: return metricdata.TypeGaugeInt64 default: - panic("unsupported gauge type") + panic("unsupported metric type") } } @@ -156,34 +147,34 @@ func createMetricOption(mos ...Options) *metricOptions { return o } -func (r *Registry) initGauge(g *gauge, name string, mos ...Options) (*gauge, error) { - val, ok := r.gauges.Load(name) +func (r *Registry) initBaseMetric(bm *baseMetric, name string, mos ...Options) (*baseMetric, error) { + val, ok := r.baseMetrics.Load(name) if ok { - existing := val.(*gauge) - if existing.gType != g.gType { - return nil, errGaugeExistsWithDiffType + existing := val.(*baseMetric) + if existing.bmType != bm.bmType { + return nil, errMetricExistsWithDiffType } } - g.start = time.Now() + bm.start = time.Now() o := createMetricOption(mos...) - g.keys = o.labelkeys - g.desc = metricdata.Descriptor{ + bm.keys = o.labelkeys + bm.desc = metricdata.Descriptor{ Name: name, Description: o.desc, Unit: o.unit, LabelKeys: o.labelkeys, - Type: gTypeToMetricType(g), + Type: bmTypeToMetricType(bm), } - r.gauges.Store(name, g) - return g, nil + r.baseMetrics.Store(name, bm) + return bm, nil } // Read reads all gauges in this registry and returns their values as metrics. func (r *Registry) Read() []*metricdata.Metric { ms := []*metricdata.Metric{} - r.gauges.Range(func(k, v interface{}) bool { - g := v.(*gauge) - ms = append(ms, g.read()) + r.baseMetrics.Range(func(k, v interface{}) bool { + bm := v.(*baseMetric) + ms = append(ms, bm.read()) return true }) return ms