Home | History | Annotate | Download | only in stats
      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.MutableViewData.ZERO_TIMESTAMP;
     21 
     22 import com.google.common.collect.Iterables;
     23 import com.google.common.collect.Lists;
     24 import io.opencensus.common.Function;
     25 import io.opencensus.common.Functions;
     26 import io.opencensus.common.Timestamp;
     27 import io.opencensus.stats.Aggregation;
     28 import io.opencensus.stats.AggregationData;
     29 import io.opencensus.stats.AggregationData.CountData;
     30 import io.opencensus.stats.AggregationData.DistributionData;
     31 import io.opencensus.stats.AggregationData.LastValueDataDouble;
     32 import io.opencensus.stats.AggregationData.LastValueDataLong;
     33 import io.opencensus.stats.AggregationData.MeanData;
     34 import io.opencensus.stats.AggregationData.SumDataDouble;
     35 import io.opencensus.stats.AggregationData.SumDataLong;
     36 import io.opencensus.stats.Measure;
     37 import io.opencensus.stats.View;
     38 import io.opencensus.stats.ViewData;
     39 import io.opencensus.stats.ViewData.AggregationWindowData;
     40 import io.opencensus.stats.ViewData.AggregationWindowData.CumulativeData;
     41 import io.opencensus.stats.ViewData.AggregationWindowData.IntervalData;
     42 import io.opencensus.tags.Tag;
     43 import io.opencensus.tags.TagContext;
     44 import io.opencensus.tags.TagValue;
     45 import java.util.ArrayList;
     46 import java.util.Collections;
     47 import java.util.Iterator;
     48 import java.util.List;
     49 import java.util.Map;
     50 import java.util.Map.Entry;
     51 import javax.annotation.Nullable;
     52 
     53 /** Stats test utilities. */
     54 final class StatsTestUtil {
     55 
     56   private static final Timestamp EMPTY = Timestamp.create(0, 0);
     57 
     58   private StatsTestUtil() {}
     59 
     60   /**
     61    * Creates an {@link AggregationData} by adding the given sequence of values, based on the
     62    * definition of the given {@link Aggregation}.
     63    *
     64    * @param aggregation the {@code Aggregation} to apply the values to.
     65    * @param values the values to add to the {@code MutableAggregation}s.
     66    * @return an {@code AggregationData}.
     67    */
     68   static AggregationData createAggregationData(
     69       Aggregation aggregation, Measure measure, double... values) {
     70     MutableAggregation mutableAggregation =
     71         RecordUtils.createMutableAggregation(aggregation, measure);
     72     for (double value : values) {
     73       mutableAggregation.add(value, Collections.<String, String>emptyMap(), EMPTY);
     74     }
     75     return mutableAggregation.toAggregationData();
     76   }
     77 
     78   /**
     79    * Compare the actual and expected AggregationMap within the given tolerance.
     80    *
     81    * @param expected the expected map.
     82    * @param actual the actual mapping from {@code List<TagValue>} to {@code AggregationData}.
     83    * @param tolerance the tolerance used for {@code double} comparison.
     84    */
     85   static void assertAggregationMapEquals(
     86       Map<? extends List<? extends TagValue>, ? extends AggregationData> actual,
     87       Map<? extends List<? extends TagValue>, ? extends AggregationData> expected,
     88       double tolerance) {
     89     assertThat(actual.keySet()).containsExactlyElementsIn(expected.keySet());
     90     for (Entry<? extends List<? extends TagValue>, ? extends AggregationData> entry :
     91         actual.entrySet()) {
     92       assertAggregationDataEquals(expected.get(entry.getKey()), entry.getValue(), tolerance);
     93     }
     94   }
     95 
     96   /**
     97    * Compare the expected and actual {@code AggregationData} within the given tolerance.
     98    *
     99    * @param expected the expected {@code AggregationData}.
    100    * @param actual the actual {@code AggregationData}.
    101    * @param tolerance the tolerance used for {@code double} comparison.
    102    */
    103   static void assertAggregationDataEquals(
    104       AggregationData expected, final AggregationData actual, final double tolerance) {
    105     expected.match(
    106         new Function<SumDataDouble, Void>() {
    107           @Override
    108           public Void apply(SumDataDouble arg) {
    109             assertThat(actual).isInstanceOf(SumDataDouble.class);
    110             assertThat(((SumDataDouble) actual).getSum()).isWithin(tolerance).of(arg.getSum());
    111             return null;
    112           }
    113         },
    114         new Function<SumDataLong, Void>() {
    115           @Override
    116           public Void apply(SumDataLong arg) {
    117             assertThat(actual).isInstanceOf(SumDataLong.class);
    118             assertThat(((SumDataLong) actual).getSum()).isEqualTo(arg.getSum());
    119             return null;
    120           }
    121         },
    122         new Function<CountData, Void>() {
    123           @Override
    124           public Void apply(CountData arg) {
    125             assertThat(actual).isInstanceOf(CountData.class);
    126             assertThat(((CountData) actual).getCount()).isEqualTo(arg.getCount());
    127             return null;
    128           }
    129         },
    130         new Function<DistributionData, Void>() {
    131           @Override
    132           public Void apply(DistributionData arg) {
    133             assertThat(actual).isInstanceOf(DistributionData.class);
    134             assertDistributionDataEquals(arg, (DistributionData) actual, tolerance);
    135             return null;
    136           }
    137         },
    138         new Function<LastValueDataDouble, Void>() {
    139           @Override
    140           public Void apply(LastValueDataDouble arg) {
    141             assertThat(actual).isInstanceOf(LastValueDataDouble.class);
    142             assertThat(((LastValueDataDouble) actual).getLastValue())
    143                 .isWithin(tolerance)
    144                 .of(arg.getLastValue());
    145             return null;
    146           }
    147         },
    148         new Function<LastValueDataLong, Void>() {
    149           @Override
    150           public Void apply(LastValueDataLong arg) {
    151             assertThat(actual).isInstanceOf(LastValueDataLong.class);
    152             assertThat(((LastValueDataLong) actual).getLastValue()).isEqualTo(arg.getLastValue());
    153             return null;
    154           }
    155         },
    156         new Function<AggregationData, Void>() {
    157           @Override
    158           public Void apply(AggregationData arg) {
    159             if (arg instanceof MeanData) {
    160               assertThat(actual).isInstanceOf(MeanData.class);
    161               assertThat(((MeanData) actual).getMean())
    162                   .isWithin(tolerance)
    163                   .of(((MeanData) arg).getMean());
    164               return null;
    165             }
    166             throw new IllegalArgumentException("Unknown Aggregation.");
    167           }
    168         });
    169   }
    170 
    171   // Create an empty ViewData with the given View.
    172   static ViewData createEmptyViewData(View view) {
    173     return ViewData.create(
    174         view,
    175         Collections.<List<TagValue>, AggregationData>emptyMap(),
    176         view.getWindow()
    177             .match(
    178                 Functions.<AggregationWindowData>returnConstant(
    179                     CumulativeData.create(ZERO_TIMESTAMP, ZERO_TIMESTAMP)),
    180                 Functions.<AggregationWindowData>returnConstant(
    181                     IntervalData.create(ZERO_TIMESTAMP)),
    182                 Functions.<AggregationWindowData>throwAssertionError()));
    183   }
    184 
    185   // Compare the expected and actual DistributionData within the given tolerance.
    186   private static void assertDistributionDataEquals(
    187       DistributionData expected, DistributionData actual, double tolerance) {
    188     assertThat(actual.getMean()).isWithin(tolerance).of(expected.getMean());
    189     assertThat(actual.getCount()).isEqualTo(expected.getCount());
    190     assertThat(actual.getMean()).isWithin(tolerance).of(expected.getMean());
    191     assertThat(actual.getSumOfSquaredDeviations())
    192         .isWithin(tolerance)
    193         .of(expected.getSumOfSquaredDeviations());
    194 
    195     if (expected.getMax() == Double.NEGATIVE_INFINITY
    196         && expected.getMin() == Double.POSITIVE_INFINITY) {
    197       assertThat(actual.getMax()).isNegativeInfinity();
    198       assertThat(actual.getMin()).isPositiveInfinity();
    199     } else {
    200       assertThat(actual.getMax()).isWithin(tolerance).of(expected.getMax());
    201       assertThat(actual.getMin()).isWithin(tolerance).of(expected.getMin());
    202     }
    203 
    204     assertThat(removeTrailingZeros((actual).getBucketCounts()))
    205         .isEqualTo(removeTrailingZeros(expected.getBucketCounts()));
    206   }
    207 
    208   @Nullable
    209   private static List<Long> removeTrailingZeros(List<Long> longs) {
    210     if (longs == null) {
    211       return null;
    212     }
    213     List<Long> truncated = new ArrayList<Long>(longs);
    214     while (!truncated.isEmpty() && Iterables.getLast(truncated) == 0) {
    215       truncated.remove(truncated.size() - 1);
    216     }
    217     return truncated;
    218   }
    219 
    220   static final class SimpleTagContext extends TagContext {
    221     private final List<Tag> tags;
    222 
    223     SimpleTagContext(Tag... tags) {
    224       this.tags = Collections.unmodifiableList(Lists.newArrayList(tags));
    225     }
    226 
    227     @Override
    228     protected Iterator<Tag> getIterator() {
    229       return tags.iterator();
    230     }
    231   }
    232 }
    233