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 }