github.com/google/cloudprober@v0.11.3/metrics/dist_test.go (about)

     1  // Copyright 2017 The Cloudprober Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package metrics
    16  
    17  import (
    18  	"math"
    19  	"reflect"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/golang/protobuf/proto"
    24  	distpb "github.com/google/cloudprober/metrics/proto"
    25  )
    26  
    27  func verifyBucketCount(t *testing.T, d *Distribution, indices []int, counts []int64) {
    28  	for _, i := range indices {
    29  		if d.bucketCounts[i] != counts[i] {
    30  			t.Errorf("For bucket with index %d and lower bound: %f, expected count: %d, got: %d", i, d.lowerBounds[i], counts[i], d.bucketCounts[i])
    31  			t.Logf("Dist: %v", d.bucketCounts)
    32  		}
    33  	}
    34  }
    35  
    36  func protoToDist(t *testing.T, testDistProtoText string) *Distribution {
    37  	testDistProto := &distpb.Dist{}
    38  	if err := proto.UnmarshalText(testDistProtoText, testDistProto); err != nil {
    39  		t.Errorf("Failed parsing distribution proto text: %s. Err: %v", testDistProtoText, err)
    40  		return nil
    41  	}
    42  	d, err := NewDistributionFromProto(testDistProto)
    43  	if err != nil {
    44  		t.Errorf("Error while creating distrubtion from the protobuf: %s. Err: %v", testDistProtoText, err)
    45  		return nil
    46  	}
    47  	return d
    48  }
    49  
    50  func TestNewDistributionFromProto(t *testing.T) {
    51  	testDistProtoText := `
    52  	  explicit_buckets: "1,2,4,8,16,32"
    53  	`
    54  	expectedLowerBounds := []float64{math.Inf(-1), 1, 2, 4, 8, 16, 32}
    55  	d := protoToDist(t, testDistProtoText)
    56  	if !reflect.DeepEqual(d.lowerBounds, expectedLowerBounds) {
    57  		t.Errorf("Unexpected lower bounds from proto. d.lowerBounds=%v, want=%v.", d.lowerBounds, expectedLowerBounds)
    58  	}
    59  }
    60  
    61  func TestNewExponentialDistribution(t *testing.T) {
    62  	rows := []struct {
    63  		name              string
    64  		base, scaleFactor float64
    65  		numBuckets        int
    66  		expectedLB        []float64
    67  		wantError         bool
    68  	}{
    69  		{
    70  			name:        "Base:2,SF:1,NB:6",
    71  			base:        2,
    72  			scaleFactor: 1,
    73  			numBuckets:  6,
    74  			expectedLB:  []float64{math.Inf(-1), 0, 1, 2, 4, 8, 16, 32},
    75  		},
    76  		{
    77  			name:        "Base:2,SF:.01,NB:7",
    78  			base:        2,
    79  			scaleFactor: 0.01,
    80  			numBuckets:  7,
    81  			expectedLB:  []float64{math.Inf(-1), 0, .01, .02, .04, .08, .16, .32, .64},
    82  		},
    83  		{
    84  			name:        "Base too low - Base:1,SF:.01,NB:7",
    85  			base:        1,
    86  			scaleFactor: 0.01,
    87  			numBuckets:  7,
    88  			wantError:   true,
    89  		},
    90  	}
    91  
    92  	for _, testRow := range rows {
    93  		d, err := NewExponentialDistribution(testRow.base, testRow.scaleFactor, testRow.numBuckets)
    94  		if (err != nil) != testRow.wantError {
    95  			t.Errorf("Row %s: error %v, want error is %v", testRow.name, err, testRow.wantError)
    96  		}
    97  		if len(testRow.expectedLB) != 0 {
    98  			if !reflect.DeepEqual(d.lowerBounds, testRow.expectedLB) {
    99  				t.Errorf("Unexpected lower bounds for exponential distribution. d.lowerBounds=%v, want=%v.", d.lowerBounds, testRow.expectedLB)
   100  			}
   101  		}
   102  	}
   103  }
   104  
   105  func TestDistAddSample(t *testing.T) {
   106  	lb := []float64{1, 5, 10, 15, 20, 30, 40, 50}
   107  	d := NewDistribution(lb)
   108  
   109  	if len(d.lowerBounds) != len(lb)+1 || len(d.bucketCounts) != len(lb)+1 {
   110  		t.Errorf("Distribution object not properly formed. Dist: %v", d)
   111  		t.FailNow()
   112  	}
   113  
   114  	for _, s := range []float64{0.5, 4, 17, 21, 3, 300} {
   115  		d.AddSample(s)
   116  	}
   117  
   118  	verifyBucketCount(t, d, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}, []int64{1, 2, 0, 0, 1, 1, 0, 0, 1})
   119  
   120  	t.Log(d.String())
   121  }
   122  
   123  func TestDistAdd(t *testing.T) {
   124  	lb := []float64{1, 5, 15, 30, 45}
   125  	d := NewDistribution(lb)
   126  
   127  	for _, s := range []float64{0.5, 4, 17} {
   128  		d.AddSample(s)
   129  	}
   130  
   131  	delta := NewDistribution(lb)
   132  	for _, s := range []float64{3.5, 21, 300} {
   133  		delta.AddSample(s)
   134  	}
   135  
   136  	if err := d.Add(delta); err != nil {
   137  		t.Error(err)
   138  	}
   139  	verifyBucketCount(t, d, []int{0, 1, 2, 3, 4, 5}, []int64{1, 2, 0, 2, 0, 1})
   140  }
   141  
   142  func TestDistSubtractCounter(t *testing.T) {
   143  	lb := []float64{1, 5, 15, 30, 45}
   144  	d := NewDistribution(lb)
   145  
   146  	for _, s := range []float64{0.5, 4, 17} {
   147  		d.AddSample(s)
   148  	}
   149  
   150  	d2 := d.Clone().(*Distribution)
   151  	for _, s := range []float64{3.5, 21, 300} {
   152  		d2.AddSample(s)
   153  	}
   154  
   155  	if wasReset, err := d2.SubtractCounter(d); err != nil || wasReset {
   156  		t.Errorf("SubtractCounter error: %v, wasReset: %v", err, wasReset)
   157  	}
   158  	verifyBucketCount(t, d2, []int{0, 1, 2, 3, 4, 5}, []int64{0, 1, 0, 1, 0, 1})
   159  }
   160  
   161  func TestDistData(t *testing.T) {
   162  	lb := []float64{1, 5, 15, 30, 45}
   163  	d := NewDistribution(lb)
   164  
   165  	for _, s := range []float64{0.5, 4, 17} {
   166  		d.AddSample(s)
   167  	}
   168  
   169  	dd := d.Data()
   170  	want := &DistributionData{
   171  		LowerBounds:  []float64{math.Inf(-1), 1, 5, 15, 30, 45},
   172  		BucketCounts: []int64{1, 1, 0, 1, 0, 0},
   173  		Count:        3,
   174  		Sum:          21.5,
   175  	}
   176  	if !reflect.DeepEqual(dd, want) {
   177  		t.Errorf("Didn't get expected data. d.Data()=%v, want: %v", dd, want)
   178  	}
   179  }
   180  
   181  func TestDistString(t *testing.T) {
   182  	lb := []float64{1, 5, 15, 30, 45}
   183  	d := NewDistribution(lb)
   184  
   185  	for _, s := range []float64{0.5, 4, 17} {
   186  		d.AddSample(s)
   187  	}
   188  
   189  	s := d.String()
   190  	want := "dist:sum:21.5|count:3|lb:-Inf,1,5,15,30,45|bc:1,1,0,1,0,0"
   191  	if s != want {
   192  		t.Errorf("String is not in expected format. d.String()=%s, want: %s", s, want)
   193  	}
   194  }
   195  
   196  func TestVerify(t *testing.T) {
   197  	d := &Distribution{}
   198  	if d.Verify() == nil {
   199  		t.Fatalf("Distribution verification didn't fail for an invalid distribution.")
   200  	}
   201  
   202  	// Now a valid distribution
   203  	lb := []float64{1, 5, 15, 30, 45}
   204  	d = NewDistribution(lb)
   205  	if d.Verify() != nil {
   206  		t.Fatalf("Distribution verification failed for a valid distribution: %s", d.String())
   207  	}
   208  
   209  	// Make it invalid by removing one element from the lower bounds.
   210  	d.lowerBounds = d.lowerBounds[1:]
   211  	if d.Verify() == nil {
   212  		t.Fatalf("Distribution verification didn't fail for an invalid distribution: %s.", d.String())
   213  	}
   214  
   215  	// Invalid distribution due to count mismatch.
   216  	d = NewDistribution(lb)
   217  	for _, s := range []float64{0.5, 4, 17} {
   218  		d.AddSample(s)
   219  	}
   220  	d.count--
   221  	if d.Verify() == nil {
   222  		t.Fatalf("Distribution verification didn't fail for an invalid distribution (count mismatch): %s.", d.String())
   223  	}
   224  }
   225  
   226  func TestParseDistFromString(t *testing.T) {
   227  	lb := []float64{1, 5, 15, 30, 45}
   228  	d := NewDistribution(lb)
   229  
   230  	for _, s := range []float64{0.5, 4, 17} {
   231  		d.AddSample(s)
   232  	}
   233  
   234  	s := d.String()
   235  	d1, err := ParseDistFromString(s)
   236  	if err != nil {
   237  		t.Fatalf("Error while parsing distribution from: %s. Err: %v", d.String(), err)
   238  	}
   239  	if d1.String() != d.String() {
   240  		t.Errorf("Didn't get the expected distribution. Got: %s, want: %s", d1.String(), d.String())
   241  	}
   242  
   243  	// Verify that parsing an invalid string results in error.
   244  	if _, err = ParseDistFromString(strings.Replace(s, "count:3", "count:a", 1)); err == nil {
   245  		t.Error("No error while parsing invalid distribution string.")
   246  	}
   247  }
   248  
   249  func BenchmarkDictStringer(b *testing.B) {
   250  	lb := []float64{1, 5, 15, 30, 45}
   251  	d := NewDistribution(lb)
   252  
   253  	for _, s := range []float64{0.5, 4, 17} {
   254  		d.AddSample(s)
   255  	}
   256  
   257  	// run the d.String() function b.N times
   258  	for n := 0; n < b.N; n++ {
   259  		_ = d.String()
   260  	}
   261  }