// Copyright 2018, 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 ( "fmt" "go.opencensus.io/metric/metricdata" "sort" "testing" "time" "github.com/google/go-cmp/cmp" ) func TestGauge(t *testing.T) { r := NewRegistry() f, _ := r.AddFloat64Gauge("TestGauge", WithLabelKeys("k1", "k2")) e, _ := f.GetEntry(metricdata.LabelValue{}, metricdata.LabelValue{}) e.Set(5) e, _ = f.GetEntry(metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}) e.Add(1) e, _ = f.GetEntry(metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}) e.Add(1) e, _ = f.GetEntry(metricdata.NewLabelValue("k1v2"), metricdata.NewLabelValue("k2v2")) e.Add(1) m := r.Read() want := []*metricdata.Metric{ { Descriptor: metricdata.Descriptor{ Name: "TestGauge", LabelKeys: []string{"k1", "k2"}, Type: metricdata.TypeGaugeFloat64, }, TimeSeries: []*metricdata.TimeSeries{ { LabelValues: []metricdata.LabelValue{ {}, {}, }, Points: []metricdata.Point{ metricdata.NewFloat64Point(time.Time{}, 5), }, }, { LabelValues: []metricdata.LabelValue{ metricdata.NewLabelValue("k1v1"), {}, }, Points: []metricdata.Point{ metricdata.NewFloat64Point(time.Time{}, 2), }, }, { LabelValues: []metricdata.LabelValue{ metricdata.NewLabelValue("k1v2"), metricdata.NewLabelValue("k2v2"), }, Points: []metricdata.Point{ metricdata.NewFloat64Point(time.Time{}, 1), }, }, }, }, } canonicalize(m) canonicalize(want) if diff := cmp.Diff(m, want, cmp.Comparer(ignoreTimes)); diff != "" { t.Errorf("-got +want: %s", diff) } } func TestGaugeMetricDescriptor(t *testing.T) { r := NewRegistry() gf, _ := r.AddFloat64Gauge("float64_gauge") compareType(gf.g.desc.Type, metricdata.TypeGaugeFloat64, t) gi, _ := r.AddInt64Gauge("int64_gauge") compareType(gi.g.desc.Type, metricdata.TypeGaugeInt64, t) dgf, _ := r.AddFloat64DerivedGauge("derived_float64_gauge") compareType(dgf.g.desc.Type, metricdata.TypeGaugeFloat64, t) dgi, _ := r.AddInt64DerivedGauge("derived_int64_gauge") compareType(dgi.g.desc.Type, metricdata.TypeGaugeInt64, t) } func compareType(got, want metricdata.Type, t *testing.T) { if got != want { t.Errorf("metricdata type: got %v, want %v\n", got, want) } } func TestGaugeMetricOptionDesc(t *testing.T) { r := NewRegistry() name := "testOptDesc" gf, _ := r.AddFloat64Gauge(name, WithDescription("test")) want := metricdata.Descriptor{ Name: name, Description: "test", Type: metricdata.TypeGaugeFloat64, } got := gf.g.desc if !cmp.Equal(got, want) { t.Errorf("metric option description: got %v, want %v\n", got, want) } } func TestGaugeMetricOptionUnit(t *testing.T) { r := NewRegistry() name := "testOptUnit" gf, _ := r.AddFloat64Gauge(name, WithUnit(metricdata.UnitMilliseconds)) want := metricdata.Descriptor{ Name: name, Unit: metricdata.UnitMilliseconds, Type: metricdata.TypeGaugeFloat64, } got := gf.g.desc if !cmp.Equal(got, want) { t.Errorf("metric descriptor: got %v, want %v\n", got, want) } } func TestGaugeMetricOptionLabelKeys(t *testing.T) { r := NewRegistry() name := "testOptUnit" gf, _ := r.AddFloat64Gauge(name, WithLabelKeys("k1", "k3")) want := metricdata.Descriptor{ Name: name, LabelKeys: []string{"k1", "k3"}, Type: metricdata.TypeGaugeFloat64, } got := gf.g.desc if !cmp.Equal(got, want) { t.Errorf("metric descriptor: got %v, want %v\n", got, want) } } func TestGaugeMetricOptionDefault(t *testing.T) { r := NewRegistry() name := "testOptUnit" gf, _ := r.AddFloat64Gauge(name) want := metricdata.Descriptor{ Name: name, Type: metricdata.TypeGaugeFloat64, } got := gf.g.desc if !cmp.Equal(got, want) { t.Errorf("metric descriptor: got %v, want %v\n", got, want) } } func TestFloat64Entry_Add(t *testing.T) { r := NewRegistry() g, _ := r.AddFloat64Gauge("g") e, _ := g.GetEntry() e.Add(0) ms := r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(float64), 0.0; got != want { t.Errorf("value = %v, want %v", got, want) } e, _ = g.GetEntry() e.Add(1) ms = r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(float64), 1.0; got != want { t.Errorf("value = %v, want %v", got, want) } e, _ = g.GetEntry() e.Add(-1) ms = r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(float64), 0.0; got != want { t.Errorf("value = %v, want %v", got, want) } } func TestFloat64Gauge_Add_NegativeTotals(t *testing.T) { r := NewRegistry() g, _ := r.AddFloat64Gauge("g") e, _ := g.GetEntry() e.Add(-1.0) ms := r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(float64), float64(0); got != want { t.Errorf("value = %v, want %v", got, want) } } func TestInt64GaugeEntry_Add(t *testing.T) { r := NewRegistry() g, _ := r.AddInt64Gauge("g") e, _ := g.GetEntry() e.Add(0) ms := r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(int64), int64(0); got != want { t.Errorf("value = %v, want %v", got, want) } e, _ = g.GetEntry() e.Add(1) ms = r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(int64), int64(1); got != want { t.Errorf("value = %v, want %v", got, want) } } func TestInt64Gauge_Add_NegativeTotals(t *testing.T) { r := NewRegistry() g, _ := r.AddInt64Gauge("g") e, _ := g.GetEntry() e.Add(-1) ms := r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(int64), int64(0); got != want { t.Errorf("value = %v, want %v", got, want) } } func TestGaugeWithSameNameDiffType(t *testing.T) { r := NewRegistry() r.AddInt64Gauge("g") _, gotErr := r.AddFloat64Gauge("g") if gotErr == nil { t.Errorf("got: nil, want error: %v", errGaugeExistsWithDiffType) } _, gotErr = r.AddInt64DerivedGauge("g") if gotErr == nil { t.Errorf("got: nil, want error: %v", errGaugeExistsWithDiffType) } _, gotErr = r.AddFloat64DerivedGauge("g") if gotErr == nil { t.Errorf("got: nil, want error: %v", errGaugeExistsWithDiffType) } } func TestGaugeWithLabelMismatch(t *testing.T) { r := NewRegistry() g, _ := r.AddInt64Gauge("g", WithLabelKeys("k1")) _, gotErr := g.GetEntry(metricdata.NewLabelValue("k1v2"), metricdata.NewLabelValue("k2v2")) if gotErr == nil { t.Errorf("got: nil, want error: %v", errKeyValueMismatch) } } func TestMapKey(t *testing.T) { cases := [][]metricdata.LabelValue{ {}, {metricdata.LabelValue{}}, {metricdata.NewLabelValue("")}, {metricdata.NewLabelValue("-")}, {metricdata.NewLabelValue(",")}, {metricdata.NewLabelValue("v1"), metricdata.NewLabelValue("v2")}, {metricdata.NewLabelValue("v1"), metricdata.LabelValue{}}, {metricdata.NewLabelValue("v1"), metricdata.LabelValue{}, metricdata.NewLabelValue(string([]byte{0}))}, {metricdata.LabelValue{}, metricdata.LabelValue{}}, } for i, tc := range cases { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { g := &gauge{ keys: make([]string, len(tc)), } mk := g.mapKey(tc) vals := g.labelValues(mk) if diff := cmp.Diff(vals, tc); diff != "" { t.Errorf("values differ after serialization -got +want: %s", diff) } }) } } func TestRaceCondition(t *testing.T) { r := NewRegistry() // start reader before adding Gauge metric. var ms = []*metricdata.Metric{} for i := 0; i < 5; i++ { go func(k int) { for j := 0; j < 5; j++ { g, _ := r.AddInt64Gauge(fmt.Sprintf("g%d%d", k, j)) e, _ := g.GetEntry() e.Add(1) } }(i) } time.Sleep(1 * time.Second) ms = r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(int64), int64(1); got != want { t.Errorf("value = %v, want %v", got, want) } } func ignoreTimes(_, _ time.Time) bool { return true } func canonicalize(ms []*metricdata.Metric) { for _, m := range ms { sort.Slice(m.TimeSeries, func(i, j int) bool { // sort time series by their label values iLabels := m.TimeSeries[i].LabelValues jLabels := m.TimeSeries[j].LabelValues for k := 0; k < len(iLabels); k++ { if !iLabels[k].Present { if jLabels[k].Present { return true } } else if !jLabels[k].Present { return false } else { return iLabels[k].Value < jLabels[k].Value } } panic("should have returned") }) } } type queueInt64 struct { size int64 } func (q *queueInt64) ToInt64() int64 { return q.size } func TestInt64DerivedGaugeEntry_Add(t *testing.T) { r := NewRegistry() q := &queueInt64{3} g, _ := r.AddInt64DerivedGauge("g", WithLabelKeys("k1", "k2")) err := g.UpsertEntry(q.ToInt64, metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}) if err != nil { t.Errorf("want: nil, got: %v", err) } ms := r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(int64), int64(3); got != want { t.Errorf("value = %v, want %v", got, want) } q.size = 5 ms = r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(int64), int64(5); got != want { t.Errorf("value = %v, want %v", got, want) } } func TestInt64DerivedGaugeEntry_AddWithNilObj(t *testing.T) { r := NewRegistry() g, _ := r.AddInt64DerivedGauge("g", WithLabelKeys("k1", "k2")) gotErr := g.UpsertEntry(nil, metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}) if gotErr == nil { t.Errorf("expected error but got nil") } } func TestInt64DerivedGaugeEntry_AddWithInvalidLabels(t *testing.T) { r := NewRegistry() q := &queueInt64{3} g, _ := r.AddInt64DerivedGauge("g", WithLabelKeys("k1", "k2")) gotErr := g.UpsertEntry(q.ToInt64, metricdata.NewLabelValue("k1v1")) if gotErr == nil { t.Errorf("expected error but got nil") } } func TestInt64DerivedGaugeEntry_Update(t *testing.T) { r := NewRegistry() q := &queueInt64{3} q2 := &queueInt64{5} g, _ := r.AddInt64DerivedGauge("g", WithLabelKeys("k1", "k2")) g.UpsertEntry(q.ToInt64, metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}) gotErr := g.UpsertEntry(q2.ToInt64, metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}) if gotErr != nil { t.Errorf("got: %v, want: nil", gotErr) } ms := r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(int64), int64(5); got != want { t.Errorf("value = %v, want %v", got, want) } } type queueFloat64 struct { size float64 } func (q *queueFloat64) ToFloat64() float64 { return q.size } func TestFloat64DerivedGaugeEntry_Add(t *testing.T) { r := NewRegistry() q := &queueFloat64{5.0} g, _ := r.AddFloat64DerivedGauge("g", WithLabelKeys("k1", "k2")) err := g.UpsertEntry(q.ToFloat64, metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}) if err != nil { t.Errorf("want: nil, got: %v", err) } ms := r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(float64), float64(5.0); got != want { t.Errorf("value = %v, want %v", got, want) } q.size = 5 ms = r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(float64), float64(5.0); got != want { t.Errorf("value = %v, want %v", got, want) } } func TestFloat64DerivedGaugeEntry_AddWithNilObj(t *testing.T) { r := NewRegistry() g, _ := r.AddFloat64DerivedGauge("g", WithLabelKeys("k1", "k2")) gotErr := g.UpsertEntry(nil, metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}) if gotErr == nil { t.Errorf("expected error but got nil") } } func TestFloat64DerivedGaugeEntry_AddWithInvalidLabels(t *testing.T) { r := NewRegistry() q := &queueFloat64{3} g, _ := r.AddFloat64DerivedGauge("g", WithLabelKeys("k1", "k2")) gotErr := g.UpsertEntry(q.ToFloat64, metricdata.NewLabelValue("k1v1")) if gotErr == nil { t.Errorf("expected error but got nil") } } func TestFloat64DerivedGaugeEntry_Update(t *testing.T) { r := NewRegistry() q := &queueFloat64{3.0} q2 := &queueFloat64{5.0} g, _ := r.AddFloat64DerivedGauge("g", WithLabelKeys("k1", "k2")) g.UpsertEntry(q.ToFloat64, metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}) gotErr := g.UpsertEntry(q2.ToFloat64, metricdata.NewLabelValue("k1v1"), metricdata.LabelValue{}) if gotErr != nil { t.Errorf("got: %v, want: nil", gotErr) } ms := r.Read() if got, want := ms[0].TimeSeries[0].Points[0].Value.(float64), float64(5.0); got != want { t.Errorf("value = %v, want %v", got, want) } }