aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYang Song <songy23@users.noreply.github.com>2018-07-10 09:25:34 -0700
committerGitHub <noreply@github.com>2018-07-10 09:25:34 -0700
commitd4031bd8386f5634a6b538f124c5f77c039ff928 (patch)
tree67d392f6b28a68348fd43bc961e5867ca180bdcb
parente59e2862e0310d0eb667ced1ebd87bb35a40c537 (diff)
downloadopencensus-java-d4031bd8386f5634a6b538f124c5f77c039ff928.tar.gz
Stats: Support recording Exemplars in the impl. (#1294)
* Stats: Support recording Exemplars in the impl. * Add more comments and tests. * Exemplar array will be null with no histogram.
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/stats/IntervalBucket.java8
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/stats/MeasureMapInternal.java16
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/stats/MeasureToViewMap.java8
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/stats/MutableAggregation.java61
-rw-r--r--impl_core/src/main/java/io/opencensus/implcore/stats/MutableViewData.java39
-rw-r--r--impl_core/src/test/java/io/opencensus/implcore/stats/IntervalBucketTest.java7
-rw-r--r--impl_core/src/test/java/io/opencensus/implcore/stats/MeasureMapInternalTest.java12
-rw-r--r--impl_core/src/test/java/io/opencensus/implcore/stats/MutableAggregationTest.java65
-rw-r--r--impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java124
-rw-r--r--impl_core/src/test/java/io/opencensus/implcore/stats/StatsTestUtil.java5
10 files changed, 283 insertions, 62 deletions
diff --git a/impl_core/src/main/java/io/opencensus/implcore/stats/IntervalBucket.java b/impl_core/src/main/java/io/opencensus/implcore/stats/IntervalBucket.java
index 65b768da..12cb319e 100644
--- a/impl_core/src/main/java/io/opencensus/implcore/stats/IntervalBucket.java
+++ b/impl_core/src/main/java/io/opencensus/implcore/stats/IntervalBucket.java
@@ -61,11 +61,15 @@ final class IntervalBucket {
}
// Puts a new value into the internal MutableAggregations, based on the TagValues.
- void record(List</*@Nullable*/ TagValue> tagValues, double value) {
+ void record(
+ List</*@Nullable*/ TagValue> tagValues,
+ double value,
+ Map<String, String> attachments,
+ Timestamp timestamp) {
if (!tagValueAggregationMap.containsKey(tagValues)) {
tagValueAggregationMap.put(tagValues, MutableViewData.createMutableAggregation(aggregation));
}
- tagValueAggregationMap.get(tagValues).add(value);
+ tagValueAggregationMap.get(tagValues).add(value, attachments, timestamp);
}
/*
diff --git a/impl_core/src/main/java/io/opencensus/implcore/stats/MeasureMapInternal.java b/impl_core/src/main/java/io/opencensus/implcore/stats/MeasureMapInternal.java
index 304ff8c4..d867b342 100644
--- a/impl_core/src/main/java/io/opencensus/implcore/stats/MeasureMapInternal.java
+++ b/impl_core/src/main/java/io/opencensus/implcore/stats/MeasureMapInternal.java
@@ -26,7 +26,6 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
-import javax.annotation.Nullable;
// TODO(songya): consider combining MeasureMapImpl and this class.
/** A map from {@link Measure}'s to measured values. */
@@ -51,15 +50,11 @@ final class MeasureMapInternal {
}
private final ArrayList<Measurement> measurements;
- @Nullable private final Map<String, String> attachments;
+ private final Map<String, String> attachments;
- private MeasureMapInternal(
- ArrayList<Measurement> measurements, @Nullable Map<String, String> attachments) {
+ private MeasureMapInternal(ArrayList<Measurement> measurements, Map<String, String> attachments) {
this.measurements = measurements;
- this.attachments =
- attachments == null
- ? null
- : Collections.unmodifiableMap(new HashMap<String, String>(attachments));
+ this.attachments = Collections.unmodifiableMap(new HashMap<String, String>(attachments));
}
/** Builder for the {@link MeasureMapInternal} class. */
@@ -91,9 +86,6 @@ final class MeasureMapInternal {
}
Builder putAttachment(String key, String value) {
- if (this.attachments == null) {
- this.attachments = new HashMap<String, String>();
- }
this.attachments.put(key, value);
return this;
}
@@ -115,7 +107,7 @@ final class MeasureMapInternal {
}
private final ArrayList<Measurement> measurements = new ArrayList<Measurement>();
- @Nullable private Map<String, String> attachments;
+ private final Map<String, String> attachments = new HashMap<String, String>();
private Builder() {}
}
diff --git a/impl_core/src/main/java/io/opencensus/implcore/stats/MeasureToViewMap.java b/impl_core/src/main/java/io/opencensus/implcore/stats/MeasureToViewMap.java
index 58d41c0d..b5917289 100644
--- a/impl_core/src/main/java/io/opencensus/implcore/stats/MeasureToViewMap.java
+++ b/impl_core/src/main/java/io/opencensus/implcore/stats/MeasureToViewMap.java
@@ -192,13 +192,13 @@ final class MeasureToViewMap {
private final TagContext tags;
private final MutableViewData view;
private final Timestamp timestamp;
- @javax.annotation.Nullable private final Map<String, String> attachments;
+ private final Map<String, String> attachments;
private RecordDoubleValueFunc(
TagContext tags,
MutableViewData view,
Timestamp timestamp,
- @javax.annotation.Nullable Map<String, String> attachments) {
+ Map<String, String> attachments) {
this.tags = tags;
this.view = view;
this.timestamp = timestamp;
@@ -216,13 +216,13 @@ final class MeasureToViewMap {
private final TagContext tags;
private final MutableViewData view;
private final Timestamp timestamp;
- @javax.annotation.Nullable private final Map<String, String> attachments;
+ private final Map<String, String> attachments;
private RecordLongValueFunc(
TagContext tags,
MutableViewData view,
Timestamp timestamp,
- @javax.annotation.Nullable Map<String, String> attachments) {
+ Map<String, String> attachments) {
this.tags = tags;
this.view = view;
this.timestamp = timestamp;
diff --git a/impl_core/src/main/java/io/opencensus/implcore/stats/MutableAggregation.java b/impl_core/src/main/java/io/opencensus/implcore/stats/MutableAggregation.java
index 51504cff..a7fa0da4 100644
--- a/impl_core/src/main/java/io/opencensus/implcore/stats/MutableAggregation.java
+++ b/impl_core/src/main/java/io/opencensus/implcore/stats/MutableAggregation.java
@@ -20,8 +20,11 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import io.opencensus.common.Function;
+import io.opencensus.common.Timestamp;
import io.opencensus.stats.Aggregation;
+import io.opencensus.stats.AggregationData.DistributionData.Exemplar;
import io.opencensus.stats.BucketBoundaries;
+import java.util.Map;
/** Mutable version of {@link Aggregation} that supports adding values. */
abstract class MutableAggregation {
@@ -35,8 +38,10 @@ abstract class MutableAggregation {
* Put a new value into the MutableAggregation.
*
* @param value new value to be added to population
+ * @param attachments the contextual information on an {@link Exemplar}
+ * @param timestamp the timestamp when the value is recorded
*/
- abstract void add(double value);
+ abstract void add(double value, Map<String, String> attachments, Timestamp timestamp);
// TODO(songya): remove this method once interval stats is completely removed.
/**
@@ -76,7 +81,7 @@ abstract class MutableAggregation {
}
@Override
- void add(double value) {
+ void add(double value, Map<String, String> attachments, Timestamp timestamp) {
sum += value;
}
@@ -123,7 +128,7 @@ abstract class MutableAggregation {
}
@Override
- void add(double value) {
+ void add(double value, Map<String, String> attachments, Timestamp timestamp) {
count++;
}
@@ -171,7 +176,7 @@ abstract class MutableAggregation {
}
@Override
- void add(double value) {
+ void add(double value, Map<String, String> attachments, Timestamp timestamp) {
count++;
sum += value;
}
@@ -236,10 +241,19 @@ abstract class MutableAggregation {
private final BucketBoundaries bucketBoundaries;
private final long[] bucketCounts;
+ // If there's a histogram (i.e bucket boundaries are not empty) in this MutableDistribution,
+ // exemplars will have the same size to bucketCounts; otherwise exemplars are null.
+ // Only the newest exemplar will be kept at each index.
+ @javax.annotation.Nullable private final Exemplar[] exemplars;
private MutableDistribution(BucketBoundaries bucketBoundaries) {
this.bucketBoundaries = bucketBoundaries;
- this.bucketCounts = new long[bucketBoundaries.getBoundaries().size() + 1];
+ int buckets = bucketBoundaries.getBoundaries().size() + 1;
+ this.bucketCounts = new long[buckets];
+ // In the implementation, each histogram bucket can have up to one exemplar, and the exemplar
+ // array is guaranteed to be in ascending order.
+ // If there's no histogram, don't record exemplars.
+ this.exemplars = bucketBoundaries.getBoundaries().isEmpty() ? null : new Exemplar[buckets];
}
/**
@@ -253,7 +267,7 @@ abstract class MutableAggregation {
}
@Override
- void add(double value) {
+ void add(double value, Map<String, String> attachments, Timestamp timestamp) {
sum += value;
count++;
@@ -277,13 +291,19 @@ abstract class MutableAggregation {
max = value;
}
- for (int i = 0; i < bucketBoundaries.getBoundaries().size(); i++) {
- if (value < bucketBoundaries.getBoundaries().get(i)) {
- bucketCounts[i]++;
- return;
+ int bucket = 0;
+ for (; bucket < bucketBoundaries.getBoundaries().size(); bucket++) {
+ if (value < bucketBoundaries.getBoundaries().get(bucket)) {
+ break;
}
}
- bucketCounts[bucketCounts.length - 1]++;
+ bucketCounts[bucket]++;
+
+ // No implicit recording for exemplars - if there are no attachments (contextual information),
+ // don't record exemplars.
+ if (!attachments.isEmpty() && exemplars != null) {
+ exemplars[bucket] = Exemplar.create(value, timestamp, attachments);
+ }
}
// We don't compute fractional MutableDistribution, it's either whole or none.
@@ -327,6 +347,18 @@ abstract class MutableAggregation {
for (int i = 0; i < bucketCounts.length; i++) {
this.bucketCounts[i] += bucketCounts[i];
}
+
+ if (exemplars != null) {
+ for (int i = 0; i < mutableDistribution.getExemplars().length; i++) {
+ Exemplar exemplar = mutableDistribution.getExemplars()[i];
+ // Assume other is always newer than this, because we combined interval buckets in time
+ // order.
+ // If there's a newer exemplar, overwrite current value.
+ if (exemplar != null) {
+ this.exemplars[i] = exemplar;
+ }
+ }
+ }
}
double getMean() {
@@ -354,6 +386,11 @@ abstract class MutableAggregation {
return bucketCounts;
}
+ @javax.annotation.Nullable
+ Exemplar[] getExemplars() {
+ return exemplars;
+ }
+
@Override
final <T> T match(
Function<? super MutableSum, T> p0,
@@ -385,7 +422,7 @@ abstract class MutableAggregation {
}
@Override
- void add(double value) {
+ void add(double value, Map<String, String> attachments, Timestamp timestamp) {
lastValue = value;
// TODO(songya): remove this once interval stats is completely removed.
if (!initialized) {
diff --git a/impl_core/src/main/java/io/opencensus/implcore/stats/MutableViewData.java b/impl_core/src/main/java/io/opencensus/implcore/stats/MutableViewData.java
index 932da745..df99ecfb 100644
--- a/impl_core/src/main/java/io/opencensus/implcore/stats/MutableViewData.java
+++ b/impl_core/src/main/java/io/opencensus/implcore/stats/MutableViewData.java
@@ -41,6 +41,7 @@ import io.opencensus.stats.Aggregation.Sum;
import io.opencensus.stats.AggregationData;
import io.opencensus.stats.AggregationData.CountData;
import io.opencensus.stats.AggregationData.DistributionData;
+import io.opencensus.stats.AggregationData.DistributionData.Exemplar;
import io.opencensus.stats.AggregationData.LastValueDataDouble;
import io.opencensus.stats.AggregationData.LastValueDataLong;
import io.opencensus.stats.AggregationData.SumDataDouble;
@@ -101,19 +102,11 @@ abstract class MutableViewData {
}
/** Record double stats with the given tags. */
- // TODO(songya): store the attachments in MutableDistribution.
abstract void record(
- TagContext context,
- double value,
- Timestamp timestamp,
- @javax.annotation.Nullable Map<String, String> attachments);
+ TagContext context, double value, Timestamp timestamp, Map<String, String> attachments);
/** Record long stats with the given tags. */
- void record(
- TagContext tags,
- long value,
- Timestamp timestamp,
- @javax.annotation.Nullable Map<String, String> attachments) {
+ void record(TagContext tags, long value, Timestamp timestamp, Map<String, String> attachments) {
// TODO(songya): shall we check for precision loss here?
record(tags, (double) value, timestamp, attachments);
}
@@ -216,17 +209,14 @@ abstract class MutableViewData {
@Override
void record(
- TagContext context,
- double value,
- Timestamp timestamp,
- @javax.annotation.Nullable Map<String, String> attachments) {
+ TagContext context, double value, Timestamp timestamp, Map<String, String> attachments) {
List</*@Nullable*/ TagValue> tagValues =
getTagValues(getTagMap(context), super.view.getColumns());
if (!tagValueAggregationMap.containsKey(tagValues)) {
tagValueAggregationMap.put(
tagValues, createMutableAggregation(super.view.getAggregation()));
}
- tagValueAggregationMap.get(tagValues).add(value);
+ tagValueAggregationMap.get(tagValues).add(value, attachments, timestamp);
}
@Override
@@ -314,15 +304,13 @@ abstract class MutableViewData {
@Override
void record(
- TagContext context,
- double value,
- Timestamp timestamp,
- @javax.annotation.Nullable Map<String, String> attachments) {
+ TagContext context, double value, Timestamp timestamp, Map<String, String> attachments) {
List</*@Nullable*/ TagValue> tagValues =
getTagValues(getTagMap(context), super.view.getColumns());
refreshBucketList(timestamp);
// It is always the last bucket that does the recording.
- CheckerFrameworkUtils.castNonNull(buckets.peekLast()).record(tagValues, value);
+ CheckerFrameworkUtils.castNonNull(buckets.peekLast())
+ .record(tagValues, value, attachments, timestamp);
}
@Override
@@ -594,13 +582,22 @@ abstract class MutableViewData {
for (long bucketCount : arg.getBucketCounts()) {
boxedBucketCounts.add(bucketCount);
}
+ List<Exemplar> exemplars = new ArrayList<Exemplar>();
+ if (arg.getExemplars() != null) {
+ for (Exemplar exemplar : arg.getExemplars()) {
+ if (exemplar != null) {
+ exemplars.add(exemplar);
+ }
+ }
+ }
return DistributionData.create(
arg.getMean(),
arg.getCount(),
arg.getMin(),
arg.getMax(),
arg.getSumOfSquaredDeviations(),
- boxedBucketCounts);
+ boxedBucketCounts,
+ exemplars);
}
private static final CreateDistributionData INSTANCE = new CreateDistributionData();
diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/IntervalBucketTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/IntervalBucketTest.java
index 10bce262..34cd434c 100644
--- a/impl_core/src/test/java/io/opencensus/implcore/stats/IntervalBucketTest.java
+++ b/impl_core/src/test/java/io/opencensus/implcore/stats/IntervalBucketTest.java
@@ -24,6 +24,7 @@ import io.opencensus.implcore.stats.MutableAggregation.MutableMean;
import io.opencensus.stats.Aggregation.Mean;
import io.opencensus.tags.TagValue;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
@@ -82,9 +83,9 @@ public class IntervalBucketTest {
IntervalBucket bucket = new IntervalBucket(START, MINUTE, MEAN);
List<TagValue> tagValues1 = Arrays.<TagValue>asList(TagValue.create("VALUE1"));
List<TagValue> tagValues2 = Arrays.<TagValue>asList(TagValue.create("VALUE2"));
- bucket.record(tagValues1, 5.0);
- bucket.record(tagValues1, 15.0);
- bucket.record(tagValues2, 10.0);
+ bucket.record(tagValues1, 5.0, Collections.<String, String>emptyMap(), START);
+ bucket.record(tagValues1, 15.0, Collections.<String, String>emptyMap(), START);
+ bucket.record(tagValues2, 10.0, Collections.<String, String>emptyMap(), START);
assertThat(bucket.getTagValueAggregationMap().keySet()).containsExactly(tagValues1, tagValues2);
MutableMean mutableMean1 = (MutableMean) bucket.getTagValueAggregationMap().get(tagValues1);
MutableMean mutableMean2 = (MutableMean) bucket.getTagValueAggregationMap().get(tagValues2);
diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/MeasureMapInternalTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/MeasureMapInternalTest.java
index c57baa0b..19e8a6c5 100644
--- a/impl_core/src/test/java/io/opencensus/implcore/stats/MeasureMapInternalTest.java
+++ b/impl_core/src/test/java/io/opencensus/implcore/stats/MeasureMapInternalTest.java
@@ -47,6 +47,18 @@ public class MeasureMapInternalTest {
}
@Test
+ public void testPutAttachment() {
+ MeasureMapInternal metrics =
+ MeasureMapInternal.builder()
+ .putAttachment("k1", "v1")
+ .putAttachment("k2", "v2")
+ .putAttachment("k1", "v3")
+ .build();
+ assertThat(metrics.getAttachments()).containsExactly("k1", "v3", "k2", "v2");
+ assertContains(metrics);
+ }
+
+ @Test
public void testCombination() {
MeasureMapInternal metrics =
MeasureMapInternal.builder()
diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/MutableAggregationTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/MutableAggregationTest.java
index 5508588a..e135dcf5 100644
--- a/impl_core/src/test/java/io/opencensus/implcore/stats/MutableAggregationTest.java
+++ b/impl_core/src/test/java/io/opencensus/implcore/stats/MutableAggregationTest.java
@@ -18,17 +18,22 @@ package io.opencensus.implcore.stats;
import static com.google.common.truth.Truth.assertThat;
+import com.google.common.collect.ImmutableList;
import io.opencensus.common.Function;
import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
import io.opencensus.implcore.stats.MutableAggregation.MutableCount;
import io.opencensus.implcore.stats.MutableAggregation.MutableDistribution;
import io.opencensus.implcore.stats.MutableAggregation.MutableLastValue;
import io.opencensus.implcore.stats.MutableAggregation.MutableMean;
import io.opencensus.implcore.stats.MutableAggregation.MutableSum;
+import io.opencensus.stats.AggregationData.DistributionData.Exemplar;
import io.opencensus.stats.BucketBoundaries;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -44,6 +49,9 @@ public class MutableAggregationTest {
private static final double TOLERANCE = 1e-6;
private static final BucketBoundaries BUCKET_BOUNDARIES =
BucketBoundaries.create(Arrays.asList(-10.0, 0.0, 10.0));
+ private static final BucketBoundaries BUCKET_BOUNDARIES_EMPTY =
+ BucketBoundaries.create(Collections.<Double>emptyList());
+ private static final Timestamp TIMESTAMP = Timestamp.create(60, 0);
@Test
public void testCreateEmpty() {
@@ -60,6 +68,11 @@ public class MutableAggregationTest {
assertThat(mutableDistribution.getMax()).isNegativeInfinity();
assertThat(mutableDistribution.getSumOfSquaredDeviations()).isWithin(TOLERANCE).of(0);
assertThat(mutableDistribution.getBucketCounts()).isEqualTo(new long[4]);
+ assertThat(mutableDistribution.getExemplars()).isEqualTo(new Exemplar[4]);
+
+ MutableDistribution mutableDistributionNoHistogram =
+ MutableDistribution.create(BUCKET_BOUNDARIES_EMPTY);
+ assertThat(mutableDistributionNoHistogram.getExemplars()).isNull();
}
@Test
@@ -91,7 +104,7 @@ public class MutableAggregationTest {
for (double value : values) {
for (MutableAggregation aggregation : aggregations) {
- aggregation.add(value);
+ aggregation.add(value, Collections.<String, String>emptyMap(), TIMESTAMP);
}
}
@@ -136,6 +149,46 @@ public class MutableAggregationTest {
}
@Test
+ public void testAdd_DistributionWithExemplarAttachments() {
+ MutableDistribution mutableDistribution = MutableDistribution.create(BUCKET_BOUNDARIES);
+ MutableDistribution mutableDistributionNoHistogram =
+ MutableDistribution.create(BUCKET_BOUNDARIES_EMPTY);
+ List<Double> values = Arrays.asList(-1.0, 1.0, -5.0, 20.0, 5.0);
+ List<Map<String, String>> attachmentsList =
+ ImmutableList.<Map<String, String>>of(
+ Collections.<String, String>singletonMap("k1", "v1"),
+ Collections.<String, String>singletonMap("k2", "v2"),
+ Collections.<String, String>singletonMap("k3", "v3"),
+ Collections.<String, String>singletonMap("k4", "v4"),
+ Collections.<String, String>singletonMap("k5", "v5"));
+ List<Timestamp> timestamps =
+ Arrays.asList(
+ Timestamp.fromMillis(500),
+ Timestamp.fromMillis(1000),
+ Timestamp.fromMillis(2000),
+ Timestamp.fromMillis(3000),
+ Timestamp.fromMillis(4000));
+ for (int i = 0; i < values.size(); i++) {
+ mutableDistribution.add(values.get(i), attachmentsList.get(i), timestamps.get(i));
+ mutableDistributionNoHistogram.add(values.get(i), attachmentsList.get(i), timestamps.get(i));
+ }
+
+ // Each bucket can only have up to one exemplar. If there are more than one exemplars in a
+ // bucket, only the last one will be kept.
+ List<Exemplar> expected =
+ Arrays.<Exemplar>asList(
+ null,
+ Exemplar.create(values.get(2), timestamps.get(2), attachmentsList.get(2)),
+ Exemplar.create(values.get(4), timestamps.get(4), attachmentsList.get(4)),
+ Exemplar.create(values.get(3), timestamps.get(3), attachmentsList.get(3)));
+ assertThat(mutableDistribution.getExemplars())
+ .asList()
+ .containsExactlyElementsIn(expected)
+ .inOrder();
+ assertThat(mutableDistributionNoHistogram.getExemplars()).isNull();
+ }
+
+ @Test
public void testMatch() {
List<MutableAggregation> aggregations =
Arrays.asList(
@@ -170,12 +223,12 @@ public class MutableAggregationTest {
for (double val : Arrays.asList(-1.0, -5.0)) {
for (MutableAggregation aggregation : aggregations1) {
- aggregation.add(val);
+ aggregation.add(val, Collections.<String, String>emptyMap(), TIMESTAMP);
}
}
for (double val : Arrays.asList(10.0, 50.0)) {
for (MutableAggregation aggregation : aggregations2) {
- aggregation.add(val);
+ aggregation.add(val, Collections.<String, String>emptyMap(), TIMESTAMP);
}
}
@@ -201,13 +254,13 @@ public class MutableAggregationTest {
MutableDistribution distribution3 = MutableDistribution.create(BUCKET_BOUNDARIES);
for (double val : Arrays.asList(5.0, -5.0)) {
- distribution1.add(val);
+ distribution1.add(val, Collections.<String, String>emptyMap(), TIMESTAMP);
}
for (double val : Arrays.asList(10.0, 20.0)) {
- distribution2.add(val);
+ distribution2.add(val, Collections.<String, String>emptyMap(), TIMESTAMP);
}
for (double val : Arrays.asList(-10.0, 15.0, -15.0, -20.0)) {
- distribution3.add(val);
+ distribution3.add(val, Collections.<String, String>emptyMap(), TIMESTAMP);
}
MutableDistribution combined = MutableDistribution.create(BUCKET_BOUNDARIES);
diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java
index 3479bbae..3c8d7af3 100644
--- a/impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java
+++ b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java
@@ -23,8 +23,16 @@ import static io.opencensus.implcore.stats.StatsTestUtil.createEmptyViewData;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import io.grpc.Context;
+import io.opencensus.common.Duration;
+import io.opencensus.common.Timestamp;
import io.opencensus.implcore.internal.SimpleEventQueue;
+import io.opencensus.stats.Aggregation.Count;
+import io.opencensus.stats.Aggregation.Distribution;
import io.opencensus.stats.Aggregation.Sum;
+import io.opencensus.stats.AggregationData.CountData;
+import io.opencensus.stats.AggregationData.DistributionData;
+import io.opencensus.stats.AggregationData.DistributionData.Exemplar;
+import io.opencensus.stats.BucketBoundaries;
import io.opencensus.stats.Measure.MeasureDouble;
import io.opencensus.stats.MeasureMap;
import io.opencensus.stats.StatsCollectionState;
@@ -62,9 +70,17 @@ public final class StatsRecorderImplTest {
private static final MeasureDouble MEASURE_DOUBLE_NO_VIEW_2 =
MeasureDouble.create("my measurement no view 2", "description", "us");
private static final View.Name VIEW_NAME = View.Name.create("my view");
+ private static final BucketBoundaries BUCKET_BOUNDARIES =
+ BucketBoundaries.create(Arrays.asList(-10.0, 0.0, 10.0));
+ private static final Distribution DISTRIBUTION = Distribution.create(BUCKET_BOUNDARIES);
+ private static final Distribution DISTRIBUTION_NO_HISTOGRAM =
+ Distribution.create(BucketBoundaries.create(Collections.<Double>emptyList()));
+ private static final Timestamp START_TIME = Timestamp.fromMillis(0);
+ private static final Duration ONE_SECOND = Duration.fromMillis(1000);
+ private final TestClock testClock = TestClock.create();
private final StatsComponent statsComponent =
- new StatsComponentImplBase(new SimpleEventQueue(), TestClock.create());
+ new StatsComponentImplBase(new SimpleEventQueue(), testClock);
private final ViewManager viewManager = statsComponent.getViewManager();
private final StatsRecorder statsRecorder = statsComponent.getStatsRecorder();
@@ -143,6 +159,112 @@ public final class StatsRecorderImplTest {
}
@Test
+ public void record_WithAttachments_Distribution() {
+ testClock.setTime(START_TIME);
+ View view =
+ View.create(VIEW_NAME, "description", MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(KEY));
+ viewManager.registerView(view);
+ recordWithAttachments();
+ ViewData viewData = viewManager.getView(VIEW_NAME);
+ assertThat(viewData).isNotNull();
+ DistributionData distributionData =
+ (DistributionData) viewData.getAggregationMap().get(Collections.singletonList(VALUE));
+ List<Exemplar> expected =
+ Arrays.asList(
+ Exemplar.create(-20.0, Timestamp.create(4, 0), Collections.singletonMap("k3", "v1")),
+ Exemplar.create(-5.0, Timestamp.create(5, 0), Collections.singletonMap("k3", "v3")),
+ Exemplar.create(1.0, Timestamp.create(2, 0), Collections.singletonMap("k2", "v2")),
+ Exemplar.create(12.0, Timestamp.create(3, 0), Collections.singletonMap("k1", "v3")));
+ assertThat(distributionData.getExemplars()).containsExactlyElementsIn(expected).inOrder();
+ }
+
+ @Test
+ public void record_WithAttachments_DistributionNoHistogram() {
+ testClock.setTime(START_TIME);
+ View view =
+ View.create(
+ VIEW_NAME,
+ "description",
+ MEASURE_DOUBLE,
+ DISTRIBUTION_NO_HISTOGRAM,
+ Arrays.asList(KEY));
+ viewManager.registerView(view);
+ recordWithAttachments();
+ ViewData viewData = viewManager.getView(VIEW_NAME);
+ assertThat(viewData).isNotNull();
+ DistributionData distributionData =
+ (DistributionData) viewData.getAggregationMap().get(Collections.singletonList(VALUE));
+ // Recording exemplar has no effect if there's no histogram.
+ assertThat(distributionData.getExemplars()).isEmpty();
+ }
+
+ @Test
+ public void record_WithAttachments_Count() {
+ testClock.setTime(START_TIME);
+ View view =
+ View.create(VIEW_NAME, "description", MEASURE_DOUBLE, Count.create(), Arrays.asList(KEY));
+ viewManager.registerView(view);
+ recordWithAttachments();
+ ViewData viewData = viewManager.getView(VIEW_NAME);
+ assertThat(viewData).isNotNull();
+ CountData countData =
+ (CountData) viewData.getAggregationMap().get(Collections.singletonList(VALUE));
+ // Recording exemplar does not affect views with an aggregation other than distribution.
+ assertThat(countData.getCount()).isEqualTo(6L);
+ }
+
+ private void recordWithAttachments() {
+ TagContext context = new SimpleTagContext(Tag.create(KEY, VALUE));
+
+ // The test Distribution has bucket boundaries [-10.0, 0.0, 10.0].
+
+ testClock.advanceTime(ONE_SECOND); // 1st second.
+ // -1.0 is in the 2nd bucket [-10.0, 0.0).
+ statsRecorder
+ .newMeasureMap()
+ .put(MEASURE_DOUBLE, -1.0)
+ .putAttachment("k1", "v1")
+ .record(context);
+
+ testClock.advanceTime(ONE_SECOND); // 2nd second.
+ // 1.0 is in the 3rd bucket [0.0, 10.0).
+ statsRecorder
+ .newMeasureMap()
+ .put(MEASURE_DOUBLE, 1.0)
+ .putAttachment("k2", "v2")
+ .record(context);
+
+ testClock.advanceTime(ONE_SECOND); // 3rd second.
+ // 12.0 is in the 4th bucket [10.0, +Inf).
+ statsRecorder
+ .newMeasureMap()
+ .put(MEASURE_DOUBLE, 12.0)
+ .putAttachment("k1", "v3")
+ .record(context);
+
+ testClock.advanceTime(ONE_SECOND); // 4th second.
+ // -20.0 is in the 1st bucket [-Inf, -10.0).
+ statsRecorder
+ .newMeasureMap()
+ .put(MEASURE_DOUBLE, -20.0)
+ .putAttachment("k3", "v1")
+ .record(context);
+
+ testClock.advanceTime(ONE_SECOND); // 5th second.
+ // -5.0 is in the 2nd bucket [-10.0, 0), should overwrite the previous exemplar -1.0.
+ statsRecorder
+ .newMeasureMap()
+ .put(MEASURE_DOUBLE, -5.0)
+ .putAttachment("k3", "v3")
+ .record(context);
+
+ testClock.advanceTime(ONE_SECOND); // 6th second.
+ // -3.0 is in the 2nd bucket [-10.0, 0), but this value doesn't come with attachments, so it
+ // shouldn't overwrite the previous exemplar (-5.0).
+ statsRecorder.newMeasureMap().put(MEASURE_DOUBLE, -3.0).record(context);
+ }
+
+ @Test
public void recordTwice() {
View view =
View.create(
diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/StatsTestUtil.java b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsTestUtil.java
index 88e1df02..6a2e6366 100644
--- a/impl_core/src/test/java/io/opencensus/implcore/stats/StatsTestUtil.java
+++ b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsTestUtil.java
@@ -22,6 +22,7 @@ import static io.opencensus.implcore.stats.MutableViewData.ZERO_TIMESTAMP;
import com.google.common.collect.Iterables;
import io.opencensus.common.Function;
import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
import io.opencensus.stats.Aggregation;
import io.opencensus.stats.AggregationData;
import io.opencensus.stats.AggregationData.CountData;
@@ -48,6 +49,8 @@ import javax.annotation.Nullable;
/** Stats test utilities. */
final class StatsTestUtil {
+ private static final Timestamp EMPTY = Timestamp.create(0, 0);
+
private StatsTestUtil() {}
/**
@@ -62,7 +65,7 @@ final class StatsTestUtil {
Aggregation aggregation, Measure measure, double... values) {
MutableAggregation mutableAggregation = MutableViewData.createMutableAggregation(aggregation);
for (double value : values) {
- mutableAggregation.add(value);
+ mutableAggregation.add(value, Collections.<String, String>emptyMap(), EMPTY);
}
return MutableViewData.createAggregationData(mutableAggregation, measure);
}