// Copyright 2017, 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 view import ( "context" "testing" "github.com/google/go-cmp/cmp" "go.opencensus.io/exemplar" "go.opencensus.io/stats" "go.opencensus.io/tag" ) func Test_View_MeasureFloat64_AggregationDistribution(t *testing.T) { k1, _ := tag.NewKey("k1") k2, _ := tag.NewKey("k2") k3, _ := tag.NewKey("k3") agg1 := Distribution(2) m := stats.Int64("Test_View_MeasureFloat64_AggregationDistribution/m1", "", stats.UnitDimensionless) view1 := &View{ TagKeys: []tag.Key{k1, k2}, Measure: m, Aggregation: agg1, } view, err := newViewInternal(view1) if err != nil { t.Fatal(err) } type tagString struct { k tag.Key v string } type record struct { f float64 tags []tagString } type testCase struct { label string records []record wantRows []*Row } tcs := []testCase{ { "1", []record{ {1, []tagString{{k1, "v1"}}}, {5, []tagString{{k1, "v1"}}}, }, []*Row{ { []tag.Tag{{Key: k1, Value: "v1"}}, &DistributionData{ Count: 2, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 8, CountPerBucket: []int64{1, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil}, }, }, }, }, { "2", []record{ {1, []tagString{{k1, "v1"}}}, {5, []tagString{{k2, "v2"}}}, }, []*Row{ { []tag.Tag{{Key: k1, Value: "v1"}}, &DistributionData{ Count: 1, Min: 1, Max: 1, Mean: 1, CountPerBucket: []int64{1, 0}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil}, }, }, { []tag.Tag{{Key: k2, Value: "v2"}}, &DistributionData{ Count: 1, Min: 5, Max: 5, Mean: 5, CountPerBucket: []int64{0, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil}, }, }, }, }, { "3", []record{ {1, []tagString{{k1, "v1"}}}, {5, []tagString{{k1, "v1"}, {k3, "v3"}}}, {1, []tagString{{k1, "v1 other"}}}, {5, []tagString{{k2, "v2"}}}, {5, []tagString{{k1, "v1"}, {k2, "v2"}}}, }, []*Row{ { []tag.Tag{{Key: k1, Value: "v1"}}, &DistributionData{ Count: 2, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 8, CountPerBucket: []int64{1, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil}, }, }, { []tag.Tag{{Key: k1, Value: "v1 other"}}, &DistributionData{ Count: 1, Min: 1, Max: 1, Mean: 1, CountPerBucket: []int64{1, 0}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil}, }, }, { []tag.Tag{{Key: k2, Value: "v2"}}, &DistributionData{ Count: 1, Min: 5, Max: 5, Mean: 5, CountPerBucket: []int64{0, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil}, }, }, { []tag.Tag{{Key: k1, Value: "v1"}, {Key: k2, Value: "v2"}}, &DistributionData{ Count: 1, Min: 5, Max: 5, Mean: 5, CountPerBucket: []int64{0, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil}, }, }, }, }, { "4", []record{ {1, []tagString{{k1, "v1 is a very long value key"}}}, {5, []tagString{{k1, "v1 is a very long value key"}, {k3, "v3"}}}, {1, []tagString{{k1, "v1 is another very long value key"}}}, {1, []tagString{{k1, "v1 is a very long value key"}, {k2, "v2 is a very long value key"}}}, {5, []tagString{{k1, "v1 is a very long value key"}, {k2, "v2 is a very long value key"}}}, {3, []tagString{{k1, "v1 is a very long value key"}, {k2, "v2 is a very long value key"}}}, {3, []tagString{{k1, "v1 is a very long value key"}, {k2, "v2 is a very long value key"}}}, }, []*Row{ { []tag.Tag{{Key: k1, Value: "v1 is a very long value key"}}, &DistributionData{ Count: 2, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 8, CountPerBucket: []int64{1, 1}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil}, }, }, { []tag.Tag{{Key: k1, Value: "v1 is another very long value key"}}, &DistributionData{ Count: 1, Min: 1, Max: 1, Mean: 1, CountPerBucket: []int64{1, 0}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil}, }, }, { []tag.Tag{{Key: k1, Value: "v1 is a very long value key"}, {Key: k2, Value: "v2 is a very long value key"}}, &DistributionData{ Count: 4, Min: 1, Max: 5, Mean: 3, SumOfSquaredDev: 2.66666666666667 * 3, CountPerBucket: []int64{1, 3}, bounds: []float64{2}, ExemplarsPerBucket: []*exemplar.Exemplar{nil, nil}, }, }, }, }, } for _, tc := range tcs { view.clearRows() view.subscribe() for _, r := range tc.records { mods := []tag.Mutator{} for _, t := range r.tags { mods = append(mods, tag.Insert(t.k, t.v)) } ctx, err := tag.New(context.Background(), mods...) if err != nil { t.Errorf("%v: New = %v", tc.label, err) } e := &exemplar.Exemplar{ Value: r.f, Attachments: exemplar.AttachmentsFromContext(ctx), } view.addSample(tag.FromContext(ctx), e) } gotRows := view.collectedRows() for i, got := range gotRows { if !containsRow(tc.wantRows, got) { t.Errorf("%v-%d: got row %v; want none", tc.label, i, got) break } } for i, want := range tc.wantRows { if !containsRow(gotRows, want) { t.Errorf("%v-%d: got none; want row %v", tc.label, i, want) break } } } } func Test_View_MeasureFloat64_AggregationSum(t *testing.T) { k1, _ := tag.NewKey("k1") k2, _ := tag.NewKey("k2") k3, _ := tag.NewKey("k3") m := stats.Int64("Test_View_MeasureFloat64_AggregationSum/m1", "", stats.UnitDimensionless) view, err := newViewInternal(&View{TagKeys: []tag.Key{k1, k2}, Measure: m, Aggregation: Sum()}) if err != nil { t.Fatal(err) } type tagString struct { k tag.Key v string } type record struct { f float64 tags []tagString } tcs := []struct { label string records []record wantRows []*Row }{ { "1", []record{ {1, []tagString{{k1, "v1"}}}, {5, []tagString{{k1, "v1"}}}, }, []*Row{ { []tag.Tag{{Key: k1, Value: "v1"}}, &SumData{Value: 6}, }, }, }, { "2", []record{ {1, []tagString{{k1, "v1"}}}, {5, []tagString{{k2, "v2"}}}, }, []*Row{ { []tag.Tag{{Key: k1, Value: "v1"}}, &SumData{Value: 1}, }, { []tag.Tag{{Key: k2, Value: "v2"}}, &SumData{Value: 5}, }, }, }, { "3", []record{ {1, []tagString{{k1, "v1"}}}, {5, []tagString{{k1, "v1"}, {k3, "v3"}}}, {1, []tagString{{k1, "v1 other"}}}, {5, []tagString{{k2, "v2"}}}, {5, []tagString{{k1, "v1"}, {k2, "v2"}}}, }, []*Row{ { []tag.Tag{{Key: k1, Value: "v1"}}, &SumData{Value: 6}, }, { []tag.Tag{{Key: k1, Value: "v1 other"}}, &SumData{Value: 1}, }, { []tag.Tag{{Key: k2, Value: "v2"}}, &SumData{Value: 5}, }, { []tag.Tag{{Key: k1, Value: "v1"}, {Key: k2, Value: "v2"}}, &SumData{Value: 5}, }, }, }, } for _, tt := range tcs { view.clearRows() view.subscribe() for _, r := range tt.records { mods := []tag.Mutator{} for _, t := range r.tags { mods = append(mods, tag.Insert(t.k, t.v)) } ctx, err := tag.New(context.Background(), mods...) if err != nil { t.Errorf("%v: New = %v", tt.label, err) } e := &exemplar.Exemplar{ Value: r.f, } view.addSample(tag.FromContext(ctx), e) } gotRows := view.collectedRows() for i, got := range gotRows { if !containsRow(tt.wantRows, got) { t.Errorf("%v-%d: got row %v; want none", tt.label, i, got) break } } for i, want := range tt.wantRows { if !containsRow(gotRows, want) { t.Errorf("%v-%d: got none; want row %v", tt.label, i, want) break } } } } func TestCanonicalize(t *testing.T) { k1, _ := tag.NewKey("k1") k2, _ := tag.NewKey("k2") m := stats.Int64("TestCanonicalize/m1", "desc desc", stats.UnitDimensionless) v := &View{TagKeys: []tag.Key{k2, k1}, Measure: m, Aggregation: Sum()} err := v.canonicalize() if err != nil { t.Fatal(err) } if got, want := v.Name, "TestCanonicalize/m1"; got != want { t.Errorf("vc.Name = %q; want %q", got, want) } if got, want := v.Description, "desc desc"; got != want { t.Errorf("vc.Description = %q; want %q", got, want) } if got, want := len(v.TagKeys), 2; got != want { t.Errorf("len(vc.TagKeys) = %d; want %d", got, want) } if got, want := v.TagKeys[0].Name(), "k1"; got != want { t.Errorf("vc.TagKeys[0].Name() = %q; want %q", got, want) } } func TestViewSortedKeys(t *testing.T) { k1, _ := tag.NewKey("a") k2, _ := tag.NewKey("b") k3, _ := tag.NewKey("c") ks := []tag.Key{k1, k3, k2} m := stats.Int64("TestViewSortedKeys/m1", "", stats.UnitDimensionless) Register(&View{ Name: "sort_keys", Description: "desc sort_keys", TagKeys: ks, Measure: m, Aggregation: Sum(), }) // Register normalizes the view by sorting the tag keys, retrieve the normalized view v := Find("sort_keys") want := []string{"a", "b", "c"} vks := v.TagKeys if len(vks) != len(want) { t.Errorf("Keys = %+v; want %+v", vks, want) } for i, v := range want { if got, want := v, vks[i].Name(); got != want { t.Errorf("View name = %q; want %q", got, want) } } } // containsRow returns true if rows contain r. func containsRow(rows []*Row, r *Row) bool { for _, x := range rows { if r.Equal(x) { return true } } return false } func TestRegisterUnregisterParity(t *testing.T) { measures := []stats.Measure{ stats.Int64("ifoo", "iFOO", "iBar"), stats.Float64("ffoo", "fFOO", "fBar"), } aggregations := []*Aggregation{ Count(), Sum(), Distribution(1, 2.0, 4.0, 8.0, 16.0), } for i := 0; i < 10; i++ { for _, m := range measures { for _, agg := range aggregations { v := &View{ Aggregation: agg, Name: "Lookup here", Measure: m, } if err := Register(v); err != nil { t.Errorf("Iteration #%d:\nMeasure: (%#v)\nAggregation (%#v)\nError: %v", i, m, agg, err) } Unregister(v) } } } } func TestRegisterAfterMeasurement(t *testing.T) { // Tests that we can register views after measurements are created and // they still take effect. m := stats.Int64(t.Name(), "", stats.UnitDimensionless) mm := m.M(1) ctx := context.Background() stats.Record(ctx, mm) v := &View{ Measure: m, Aggregation: Count(), } if err := Register(v); err != nil { t.Fatal(err) } rows, err := RetrieveData(v.Name) if err != nil { t.Fatal(err) } if len(rows) > 0 { t.Error("View should not have data") } stats.Record(ctx, mm) rows, err = RetrieveData(v.Name) if err != nil { t.Fatal(err) } if len(rows) == 0 { t.Error("View should have data") } } func TestViewRegister_negativeBucketBounds(t *testing.T) { m := stats.Int64("TestViewRegister_negativeBucketBounds", "", "") v := &View{ Measure: m, Aggregation: Distribution(-1, 2), } err := Register(v) if err != ErrNegativeBucketBounds { t.Errorf("Expected ErrNegativeBucketBounds, got %v", err) } } func TestViewRegister_sortBuckets(t *testing.T) { m := stats.Int64("TestViewRegister_sortBuckets", "", "") v := &View{ Measure: m, Aggregation: Distribution(2, 1), } err := Register(v) if err != nil { t.Fatalf("Unexpected err %s", err) } want := []float64{1, 2} if diff := cmp.Diff(v.Aggregation.Buckets, want); diff != "" { t.Errorf("buckets differ -got +want: %s", diff) } } func TestViewRegister_dropZeroBuckets(t *testing.T) { m := stats.Int64("TestViewRegister_dropZeroBuckets", "", "") v := &View{ Measure: m, Aggregation: Distribution(2, 0, 1), } err := Register(v) if err != nil { t.Fatalf("Unexpected err %s", err) } want := []float64{1, 2} if diff := cmp.Diff(v.Aggregation.Buckets, want); diff != "" { t.Errorf("buckets differ -got +want: %s", diff) } }