1 /* 2 * Copyright 2017, OpenCensus Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.opencensus.implcore.stats; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static io.opencensus.implcore.stats.StatsTestUtil.assertAggregationDataEquals; 21 22 import com.google.common.collect.ImmutableList; 23 import io.opencensus.common.Timestamp; 24 import io.opencensus.implcore.stats.MutableAggregation.MutableCount; 25 import io.opencensus.implcore.stats.MutableAggregation.MutableDistribution; 26 import io.opencensus.implcore.stats.MutableAggregation.MutableLastValueDouble; 27 import io.opencensus.implcore.stats.MutableAggregation.MutableLastValueLong; 28 import io.opencensus.implcore.stats.MutableAggregation.MutableMean; 29 import io.opencensus.implcore.stats.MutableAggregation.MutableSumDouble; 30 import io.opencensus.implcore.stats.MutableAggregation.MutableSumLong; 31 import io.opencensus.metrics.export.Distribution; 32 import io.opencensus.metrics.export.Distribution.Bucket; 33 import io.opencensus.metrics.export.Distribution.BucketOptions; 34 import io.opencensus.metrics.export.Point; 35 import io.opencensus.metrics.export.Value; 36 import io.opencensus.stats.AggregationData; 37 import io.opencensus.stats.AggregationData.CountData; 38 import io.opencensus.stats.AggregationData.DistributionData; 39 import io.opencensus.stats.AggregationData.DistributionData.Exemplar; 40 import io.opencensus.stats.AggregationData.LastValueDataDouble; 41 import io.opencensus.stats.AggregationData.LastValueDataLong; 42 import io.opencensus.stats.AggregationData.MeanData; 43 import io.opencensus.stats.AggregationData.SumDataDouble; 44 import io.opencensus.stats.AggregationData.SumDataLong; 45 import io.opencensus.stats.BucketBoundaries; 46 import java.util.Arrays; 47 import java.util.Collections; 48 import java.util.List; 49 import java.util.Map; 50 import org.junit.Rule; 51 import org.junit.Test; 52 import org.junit.rules.ExpectedException; 53 import org.junit.runner.RunWith; 54 import org.junit.runners.JUnit4; 55 56 /** Unit tests for {@link io.opencensus.implcore.stats.MutableAggregation}. */ 57 @RunWith(JUnit4.class) 58 public class MutableAggregationTest { 59 60 @Rule public ExpectedException thrown = ExpectedException.none(); 61 62 private static final double TOLERANCE = 1e-6; 63 private static final BucketBoundaries BUCKET_BOUNDARIES = 64 BucketBoundaries.create(Arrays.asList(-10.0, 0.0, 10.0)); 65 private static final BucketBoundaries BUCKET_BOUNDARIES_EMPTY = 66 BucketBoundaries.create(Collections.<Double>emptyList()); 67 private static final Timestamp TIMESTAMP = Timestamp.create(60, 0); 68 69 @Test testCreateEmpty()70 public void testCreateEmpty() { 71 assertThat(MutableSumDouble.create().getSum()).isWithin(TOLERANCE).of(0); 72 assertThat(MutableSumLong.create().getSum()).isWithin(TOLERANCE).of(0); 73 assertThat(MutableCount.create().getCount()).isEqualTo(0); 74 assertThat(MutableMean.create().getMean()).isWithin(TOLERANCE).of(0); 75 assertThat(MutableLastValueDouble.create().getLastValue()).isNaN(); 76 assertThat(MutableLastValueLong.create().getLastValue()).isNaN(); 77 78 BucketBoundaries bucketBoundaries = BucketBoundaries.create(Arrays.asList(0.1, 2.2, 33.3)); 79 MutableDistribution mutableDistribution = MutableDistribution.create(bucketBoundaries); 80 assertThat(mutableDistribution.getMean()).isWithin(TOLERANCE).of(0); 81 assertThat(mutableDistribution.getCount()).isEqualTo(0); 82 assertThat(mutableDistribution.getMin()).isPositiveInfinity(); 83 assertThat(mutableDistribution.getMax()).isNegativeInfinity(); 84 assertThat(mutableDistribution.getSumOfSquaredDeviations()).isWithin(TOLERANCE).of(0); 85 assertThat(mutableDistribution.getBucketCounts()).isEqualTo(new long[4]); 86 assertThat(mutableDistribution.getExemplars()).isEqualTo(new Exemplar[4]); 87 88 MutableDistribution mutableDistributionNoHistogram = 89 MutableDistribution.create(BUCKET_BOUNDARIES_EMPTY); 90 assertThat(mutableDistributionNoHistogram.getExemplars()).isNull(); 91 } 92 93 @Test testNullBucketBoundaries()94 public void testNullBucketBoundaries() { 95 thrown.expect(NullPointerException.class); 96 thrown.expectMessage("bucketBoundaries should not be null."); 97 MutableDistribution.create(null); 98 } 99 100 @Test testNoBoundaries()101 public void testNoBoundaries() { 102 List<Double> buckets = Arrays.asList(); 103 MutableDistribution noBoundaries = MutableDistribution.create(BucketBoundaries.create(buckets)); 104 assertThat(noBoundaries.getBucketCounts().length).isEqualTo(1); 105 assertThat(noBoundaries.getBucketCounts()[0]).isEqualTo(0); 106 } 107 108 @Test testAdd()109 public void testAdd() { 110 List<MutableAggregation> aggregations = 111 Arrays.asList( 112 MutableSumDouble.create(), 113 MutableSumLong.create(), 114 MutableCount.create(), 115 MutableMean.create(), 116 MutableDistribution.create(BUCKET_BOUNDARIES), 117 MutableLastValueDouble.create(), 118 MutableLastValueLong.create()); 119 120 List<Double> values = Arrays.asList(-1.0, 1.0, -5.0, 20.0, 5.0); 121 122 for (double value : values) { 123 for (MutableAggregation aggregation : aggregations) { 124 aggregation.add(value, Collections.<String, String>emptyMap(), TIMESTAMP); 125 } 126 } 127 128 assertAggregationDataEquals( 129 aggregations.get(0).toAggregationData(), 130 AggregationData.SumDataDouble.create(20.0), 131 TOLERANCE); 132 assertAggregationDataEquals( 133 aggregations.get(1).toAggregationData(), AggregationData.SumDataLong.create(20), TOLERANCE); 134 assertAggregationDataEquals( 135 aggregations.get(2).toAggregationData(), AggregationData.CountData.create(5), TOLERANCE); 136 assertAggregationDataEquals( 137 aggregations.get(3).toAggregationData(), 138 AggregationData.MeanData.create(4.0, 5), 139 TOLERANCE); 140 assertAggregationDataEquals( 141 aggregations.get(4).toAggregationData(), 142 AggregationData.DistributionData.create( 143 4.0, 5, -5.0, 20.0, 372, Arrays.asList(0L, 2L, 2L, 1L)), 144 TOLERANCE); 145 assertAggregationDataEquals( 146 aggregations.get(5).toAggregationData(), 147 AggregationData.LastValueDataDouble.create(5.0), 148 TOLERANCE); 149 assertAggregationDataEquals( 150 aggregations.get(6).toAggregationData(), 151 AggregationData.LastValueDataLong.create(5), 152 TOLERANCE); 153 } 154 155 @Test testAdd_DistributionWithExemplarAttachments()156 public void testAdd_DistributionWithExemplarAttachments() { 157 MutableDistribution mutableDistribution = MutableDistribution.create(BUCKET_BOUNDARIES); 158 MutableDistribution mutableDistributionNoHistogram = 159 MutableDistribution.create(BUCKET_BOUNDARIES_EMPTY); 160 List<Double> values = Arrays.asList(-1.0, 1.0, -5.0, 20.0, 5.0); 161 List<Map<String, String>> attachmentsList = 162 ImmutableList.<Map<String, String>>of( 163 Collections.<String, String>singletonMap("k1", "v1"), 164 Collections.<String, String>singletonMap("k2", "v2"), 165 Collections.<String, String>singletonMap("k3", "v3"), 166 Collections.<String, String>singletonMap("k4", "v4"), 167 Collections.<String, String>singletonMap("k5", "v5")); 168 List<Timestamp> timestamps = 169 Arrays.asList( 170 Timestamp.fromMillis(500), 171 Timestamp.fromMillis(1000), 172 Timestamp.fromMillis(2000), 173 Timestamp.fromMillis(3000), 174 Timestamp.fromMillis(4000)); 175 for (int i = 0; i < values.size(); i++) { 176 mutableDistribution.add(values.get(i), attachmentsList.get(i), timestamps.get(i)); 177 mutableDistributionNoHistogram.add(values.get(i), attachmentsList.get(i), timestamps.get(i)); 178 } 179 180 // Each bucket can only have up to one exemplar. If there are more than one exemplars in a 181 // bucket, only the last one will be kept. 182 List<Exemplar> expected = 183 Arrays.<Exemplar>asList( 184 null, 185 Exemplar.create(values.get(2), timestamps.get(2), attachmentsList.get(2)), 186 Exemplar.create(values.get(4), timestamps.get(4), attachmentsList.get(4)), 187 Exemplar.create(values.get(3), timestamps.get(3), attachmentsList.get(3))); 188 assertThat(mutableDistribution.getExemplars()) 189 .asList() 190 .containsExactlyElementsIn(expected) 191 .inOrder(); 192 assertThat(mutableDistributionNoHistogram.getExemplars()).isNull(); 193 } 194 195 @Test testCombine_SumCountMean()196 public void testCombine_SumCountMean() { 197 // combine() for Mutable Sum, Count and Mean will pick up fractional stats 198 List<MutableAggregation> aggregations1 = 199 Arrays.asList( 200 MutableSumDouble.create(), 201 MutableSumLong.create(), 202 MutableCount.create(), 203 MutableMean.create()); 204 List<MutableAggregation> aggregations2 = 205 Arrays.asList( 206 MutableSumDouble.create(), 207 MutableSumLong.create(), 208 MutableCount.create(), 209 MutableMean.create()); 210 211 for (double val : Arrays.asList(-1.0, -5.0)) { 212 for (MutableAggregation aggregation : aggregations1) { 213 aggregation.add(val, Collections.<String, String>emptyMap(), TIMESTAMP); 214 } 215 } 216 for (double val : Arrays.asList(10.0, 50.0)) { 217 for (MutableAggregation aggregation : aggregations2) { 218 aggregation.add(val, Collections.<String, String>emptyMap(), TIMESTAMP); 219 } 220 } 221 222 List<MutableAggregation> combined = 223 Arrays.asList( 224 MutableSumDouble.create(), 225 MutableSumLong.create(), 226 MutableCount.create(), 227 MutableMean.create()); 228 double fraction1 = 1.0; 229 double fraction2 = 0.6; 230 for (int i = 0; i < combined.size(); i++) { 231 combined.get(i).combine(aggregations1.get(i), fraction1); 232 combined.get(i).combine(aggregations2.get(i), fraction2); 233 } 234 235 assertThat(((MutableSumDouble) combined.get(0)).getSum()).isWithin(TOLERANCE).of(30); 236 assertThat(((MutableSumLong) combined.get(1)).getSum()).isWithin(TOLERANCE).of(30); 237 assertThat(((MutableCount) combined.get(2)).getCount()).isEqualTo(3); 238 assertThat(((MutableMean) combined.get(3)).getMean()).isWithin(TOLERANCE).of(10); 239 } 240 241 @Test testCombine_Distribution()242 public void testCombine_Distribution() { 243 // combine() for Mutable Distribution will ignore fractional stats 244 MutableDistribution distribution1 = MutableDistribution.create(BUCKET_BOUNDARIES); 245 MutableDistribution distribution2 = MutableDistribution.create(BUCKET_BOUNDARIES); 246 MutableDistribution distribution3 = MutableDistribution.create(BUCKET_BOUNDARIES); 247 248 for (double val : Arrays.asList(5.0, -5.0)) { 249 distribution1.add(val, Collections.<String, String>emptyMap(), TIMESTAMP); 250 } 251 for (double val : Arrays.asList(10.0, 20.0)) { 252 distribution2.add(val, Collections.<String, String>emptyMap(), TIMESTAMP); 253 } 254 for (double val : Arrays.asList(-10.0, 15.0, -15.0, -20.0)) { 255 distribution3.add(val, Collections.<String, String>emptyMap(), TIMESTAMP); 256 } 257 258 MutableDistribution combined = MutableDistribution.create(BUCKET_BOUNDARIES); 259 combined.combine(distribution1, 1.0); // distribution1 will be combined 260 combined.combine(distribution2, 0.6); // distribution2 will be ignored 261 verifyMutableDistribution(combined, 0, 2, -5, 5, 50.0, new long[] {0, 1, 1, 0}, TOLERANCE); 262 263 combined.combine(distribution2, 1.0); // distribution2 will be combined 264 verifyMutableDistribution(combined, 7.5, 4, -5, 20, 325.0, new long[] {0, 1, 1, 2}, TOLERANCE); 265 266 combined.combine(distribution3, 1.0); // distribution3 will be combined 267 verifyMutableDistribution(combined, 0, 8, -20, 20, 1500.0, new long[] {2, 2, 1, 3}, TOLERANCE); 268 } 269 270 @Test mutableAggregation_ToAggregationData()271 public void mutableAggregation_ToAggregationData() { 272 assertThat(MutableSumDouble.create().toAggregationData()).isEqualTo(SumDataDouble.create(0)); 273 assertThat(MutableSumLong.create().toAggregationData()).isEqualTo(SumDataLong.create(0)); 274 assertThat(MutableCount.create().toAggregationData()).isEqualTo(CountData.create(0)); 275 assertThat(MutableMean.create().toAggregationData()).isEqualTo(MeanData.create(0, 0)); 276 assertThat(MutableDistribution.create(BUCKET_BOUNDARIES).toAggregationData()) 277 .isEqualTo( 278 DistributionData.create( 279 0, 280 0, 281 Double.POSITIVE_INFINITY, 282 Double.NEGATIVE_INFINITY, 283 0, 284 Arrays.asList(0L, 0L, 0L, 0L))); 285 assertThat(MutableLastValueDouble.create().toAggregationData()) 286 .isEqualTo(LastValueDataDouble.create(Double.NaN)); 287 assertThat(MutableLastValueLong.create().toAggregationData()) 288 .isEqualTo(LastValueDataLong.create(0)); 289 } 290 291 @Test mutableAggregation_ToPoint()292 public void mutableAggregation_ToPoint() { 293 assertThat(MutableSumDouble.create().toPoint(TIMESTAMP)) 294 .isEqualTo(Point.create(Value.doubleValue(0), TIMESTAMP)); 295 assertThat(MutableSumLong.create().toPoint(TIMESTAMP)) 296 .isEqualTo(Point.create(Value.longValue(0), TIMESTAMP)); 297 assertThat(MutableCount.create().toPoint(TIMESTAMP)) 298 .isEqualTo(Point.create(Value.longValue(0), TIMESTAMP)); 299 assertThat(MutableMean.create().toPoint(TIMESTAMP)) 300 .isEqualTo(Point.create(Value.doubleValue(0), TIMESTAMP)); 301 302 thrown.expect(IllegalArgumentException.class); 303 thrown.expectMessage("bucket boundary should be > 0"); 304 assertThat(MutableDistribution.create(BUCKET_BOUNDARIES).toPoint(TIMESTAMP)) 305 .isEqualTo( 306 Point.create( 307 Value.distributionValue( 308 Distribution.create( 309 0, 310 0, 311 0, 312 BucketOptions.explicitOptions(BUCKET_BOUNDARIES.getBoundaries()), 313 Arrays.asList( 314 Bucket.create(0), 315 Bucket.create(0), 316 Bucket.create(0), 317 Bucket.create(0)))), 318 TIMESTAMP)); 319 } 320 verifyMutableDistribution( MutableDistribution mutableDistribution, double mean, long count, double min, double max, double sumOfSquaredDeviations, long[] bucketCounts, double tolerance)321 private static void verifyMutableDistribution( 322 MutableDistribution mutableDistribution, 323 double mean, 324 long count, 325 double min, 326 double max, 327 double sumOfSquaredDeviations, 328 long[] bucketCounts, 329 double tolerance) { 330 assertThat(mutableDistribution.getMean()).isWithin(tolerance).of(mean); 331 assertThat(mutableDistribution.getCount()).isEqualTo(count); 332 assertThat(mutableDistribution.getMin()).isWithin(tolerance).of(min); 333 assertThat(mutableDistribution.getMax()).isWithin(tolerance).of(max); 334 assertThat(mutableDistribution.getSumOfSquaredDeviations()) 335 .isWithin(tolerance) 336 .of(sumOfSquaredDeviations); 337 assertThat(mutableDistribution.getBucketCounts()).isEqualTo(bucketCounts); 338 } 339 } 340