Skip to content
This repository has been archived by the owner on Dec 23, 2023. It is now read-only.

ignore negative bucket bounds #1499

Merged
merged 9 commits into from
Nov 1, 2018
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
- Add Gauges (`DoubleGauge`, `LongGauge`, `DerivedDoubleGauge`, `DerivedLongGauge`) APIs.
- Update `opencensus-contrib-log-correlation-log4j2` to match the
[OpenCensus log correlation spec](https://github.com/census-instrumentation/opencensus-specs/blob/master/trace/LogCorrelation.md).
- The histogram bucket boundaries (`BucketBoundaries`) and values (`Count` and `Sum`) are no longer
supported for negative values. The Record API drops the negative `value` and logs the warning.
This could be a breaking change if you are recording negative value for any `measure`.

## 0.16.1 - 2018-09-18
- Fix ClassCastException in Log4j log correlation
Expand Down
41 changes: 37 additions & 4 deletions api/src/main/java/io/opencensus/stats/BucketBoundaries.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;

/**
Expand All @@ -32,6 +34,8 @@
@AutoValue
public abstract class BucketBoundaries {

private static final Logger logger = Logger.getLogger(BucketBoundaries.class.getName());

/**
* Returns a {@code BucketBoundaries} with the given buckets.
*
Expand All @@ -46,14 +50,43 @@ public static final BucketBoundaries create(List<Double> bucketBoundaries) {
List<Double> bucketBoundariesCopy = new ArrayList<Double>(bucketBoundaries); // Deep copy.
// Check if sorted.
if (bucketBoundariesCopy.size() > 1) {
double lower = bucketBoundariesCopy.get(0);
double previous = bucketBoundariesCopy.get(0);
for (int i = 1; i < bucketBoundariesCopy.size(); i++) {
double next = bucketBoundariesCopy.get(i);
Utils.checkArgument(lower < next, "Bucket boundaries not sorted.");
lower = next;
Utils.checkArgument(previous < next, "Bucket boundaries not sorted.");
previous = next;
}
}
return new AutoValue_BucketBoundaries(Collections.unmodifiableList(bucketBoundariesCopy));
return new AutoValue_BucketBoundaries(
Collections.unmodifiableList(dropNegativeBucketBounds(bucketBoundariesCopy)));
}

private static List<Double> dropNegativeBucketBounds(List<Double> bucketBoundaries) {
mayurkale22 marked this conversation as resolved.
Show resolved Hide resolved
// Negative values (BucketBounds) are currently not supported by any of the backends
// that OC supports.
int negativeBucketBounds = 0;
int zeroBucketBounds = 0;
for (Double value : bucketBoundaries) {
if (value <= 0) {
if (value == 0) {
zeroBucketBounds++;
} else {
negativeBucketBounds++;
}
} else {
break;
}
}

if (negativeBucketBounds > 0) {
logger.log(
Level.WARNING,
"Dropping "
+ negativeBucketBounds
+ " negative bucket boundaries, the values must be strictly > 0.");
}
return bucketBoundaries.subList(
negativeBucketBounds + zeroBucketBounds, bucketBoundaries.size());
}

/**
Expand Down
23 changes: 18 additions & 5 deletions api/src/main/java/io/opencensus/stats/NoopStats.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;
Expand Down Expand Up @@ -66,8 +68,8 @@ static StatsRecorder getNoopStatsRecorder() {
*
* @return a {@code MeasureMap} that ignores all calls to {@code MeasureMap#put}.
*/
static MeasureMap getNoopMeasureMap() {
return NoopMeasureMap.INSTANCE;
static MeasureMap newNoopMeasureMap() {
return new NoopMeasureMap();
}

/**
Expand Down Expand Up @@ -116,21 +118,27 @@ private static final class NoopStatsRecorder extends StatsRecorder {

@Override
public MeasureMap newMeasureMap() {
return getNoopMeasureMap();
return newNoopMeasureMap();
}
}

@Immutable
private static final class NoopMeasureMap extends MeasureMap {
static final MeasureMap INSTANCE = new NoopMeasureMap();
private static final Logger logger = Logger.getLogger(NoopMeasureMap.class.getName());
private boolean hasUnsupportedValues;

@Override
public MeasureMap put(MeasureDouble measure, double value) {
if (value < 0) {
hasUnsupportedValues = true;
}
return this;
}

@Override
public MeasureMap put(MeasureLong measure, long value) {
if (value < 0) {
hasUnsupportedValues = true;
}
return this;
}

Expand All @@ -140,6 +148,11 @@ public void record() {}
@Override
public void record(TagContext tags) {
Utils.checkNotNull(tags, "tags");

if (hasUnsupportedValues) {
// drop all the recorded values
logger.log(Level.WARNING, "Dropping values, value to record must be non-negative.");
mayurkale22 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

Expand Down
7 changes: 4 additions & 3 deletions api/src/test/java/io/opencensus/stats/AggregationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,11 @@ public void testEquals() {
.addEqualityGroup(Count.create(), Count.create())
.addEqualityGroup(
Distribution.create(BucketBoundaries.create(Arrays.asList(-10.0, 1.0, 5.0))),
Distribution.create(BucketBoundaries.create(Arrays.asList(-10.0, 1.0, 5.0))))
.addEqualityGroup(
Distribution.create(BucketBoundaries.create(Arrays.asList(0.0, 1.0, 5.0))),
Distribution.create(BucketBoundaries.create(Arrays.asList(-10.0, 1.0, 5.0))),
Distribution.create(BucketBoundaries.create(Arrays.asList(0.0, 1.0, 5.0))))
.addEqualityGroup(
Distribution.create(BucketBoundaries.create(Arrays.asList(1.0, 2.0, 5.0))),
Distribution.create(BucketBoundaries.create(Arrays.asList(1.0, 2.0, 5.0))))
.addEqualityGroup(Mean.create(), Mean.create())
.addEqualityGroup(LastValue.create(), LastValue.create())
.testEquals();
Expand Down
20 changes: 18 additions & 2 deletions api/src/test/java/io/opencensus/stats/BucketBoundariesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,24 @@ public class BucketBoundariesTest {
@Test
public void testConstructBoundaries() {
List<Double> buckets = Arrays.asList(0.0, 1.0, 2.0);
List<Double> expectedBuckets = Arrays.asList(1.0, 2.0);
BucketBoundaries bucketBoundaries = BucketBoundaries.create(buckets);
assertThat(bucketBoundaries.getBoundaries()).isEqualTo(buckets);
assertThat(bucketBoundaries.getBoundaries()).isEqualTo(expectedBuckets);
}

@Test
public void testConstructBoundaries_IgnoreNegativeBounds() {
List<Double> buckets = Arrays.asList(-5.0, -1.0, 1.0, 2.0);
List<Double> expectedBuckets = Arrays.asList(1.0, 2.0);
BucketBoundaries bucketBoundaries = BucketBoundaries.create(buckets);
assertThat(bucketBoundaries.getBoundaries()).isEqualTo(expectedBuckets);
}

@Test
public void testConstructBoundaries_IgnoreZeroAndNegativeBounds() {
List<Double> buckets = Arrays.asList(-5.0, -2.0, -1.0, 0.0);
BucketBoundaries bucketBoundaries = BucketBoundaries.create(buckets);
assertThat(bucketBoundaries.getBoundaries()).isEmpty();
}

@Test
Expand All @@ -50,7 +66,7 @@ public void testBoundariesDoesNotChangeWithOriginalList() {
BucketBoundaries bucketBoundaries = BucketBoundaries.create(original);
original.set(2, 3.0);
original.add(4.0);
List<Double> expected = Arrays.asList(0.0, 1.0, 2.0);
List<Double> expected = Arrays.asList(1.0, 2.0);
assertThat(bucketBoundaries.getBoundaries()).isNotEqualTo(original);
assertThat(bucketBoundaries.getBoundaries()).isEqualTo(expected);
}
Expand Down
5 changes: 5 additions & 0 deletions api/src/test/java/io/opencensus/stats/NoopStatsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ public void noopStatsRecorder_PutAttachmentNullValue() {
measureMap.putAttachment("key", null);
}

@Test
public void noopStatsRecorder_PutNegativeValue() {
NoopStats.getNoopStatsRecorder().newMeasureMap().put(MEASURE, -5).record(tagContext);
}

// The NoopStatsRecorder should do nothing, so this test just checks that record doesn't throw an
// exception.
@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public void constants() {
.getBucketBoundaries()
.getBoundaries())
.containsExactly(
0.0,
1024.0,
2048.0,
4096.0,
Expand All @@ -59,9 +58,9 @@ public void constants() {
.getBucketBoundaries()
.getBoundaries())
.containsExactly(
0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 13.0, 16.0, 20.0, 25.0, 30.0, 40.0, 50.0,
65.0, 80.0, 100.0, 130.0, 160.0, 200.0, 250.0, 300.0, 400.0, 500.0, 650.0, 800.0,
1000.0, 2000.0, 5000.0, 10000.0, 20000.0, 50000.0, 100000.0)
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 13.0, 16.0, 20.0, 25.0, 30.0, 40.0, 50.0, 65.0,
80.0, 100.0, 130.0, 160.0, 200.0, 250.0, 300.0, 400.0, 500.0, 650.0, 800.0, 1000.0,
2000.0, 5000.0, 10000.0, 20000.0, 50000.0, 100000.0)
.inOrder();

// Test views.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public class StatszZPageHandlerTest {
0.2,
16.3,
234.56,
Arrays.asList(0L, 1L, 1L, 2L, 1L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L));
Arrays.asList(1L, 1L, 2L, 1L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L));
private static final AggregationData.DistributionData DISTRIBUTION_DATA_2 =
AggregationData.DistributionData.create(
7.9,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@
import io.opencensus.stats.MeasureMap;
import io.opencensus.tags.TagContext;
import io.opencensus.tags.unsafe.ContextUtils;
import java.util.logging.Level;
import java.util.logging.Logger;

/** Implementation of {@link MeasureMap}. */
final class MeasureMapImpl extends MeasureMap {
private static final Logger logger = Logger.getLogger(MeasureMapImpl.class.getName());

private final StatsManager statsManager;
private final MeasureMapInternal.Builder builder = MeasureMapInternal.builder();
private volatile boolean hasUnsupportedValues;

static MeasureMapImpl create(StatsManager statsManager) {
return new MeasureMapImpl(statsManager);
Expand All @@ -37,12 +42,18 @@ private MeasureMapImpl(StatsManager statsManager) {

@Override
public MeasureMapImpl put(MeasureDouble measure, double value) {
if (value < 0) {
hasUnsupportedValues = true;
}
builder.put(measure, value);
return this;
}

@Override
public MeasureMapImpl put(MeasureLong measure, long value) {
if (value < 0) {
mayurkale22 marked this conversation as resolved.
Show resolved Hide resolved
hasUnsupportedValues = true;
}
builder.put(measure, value);
return this;
}
Expand All @@ -61,6 +72,11 @@ public void record() {

@Override
public void record(TagContext tags) {
if (hasUnsupportedValues) {
// drop all the recorded values
logger.log(Level.WARNING, "Dropping values, value to record must be non-negative.");
return;
}
statsManager.record(tags, builder.build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,6 @@ Point toPoint(Timestamp timestamp) {
buckets.add(metricBucket);
}

// TODO(mayurkale): Drop the first bucket when converting to metrics.
// Reason: In Stats API, bucket bounds begin with -infinity (first bucket is (-infinity, 0)).
BucketOptions bucketOptions = BucketOptions.explicitOptions(bucketBoundaries.getBoundaries());

return Point.create(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,7 @@ public void testAdd() {
TOLERANCE);
assertAggregationDataEquals(
aggregations.get(4).toAggregationData(),
AggregationData.DistributionData.create(
4.0, 5, -5.0, 20.0, 372, Arrays.asList(0L, 2L, 2L, 1L)),
AggregationData.DistributionData.create(4.0, 5, -5.0, 20.0, 372, Arrays.asList(4L, 1L)),
TOLERANCE);
assertAggregationDataEquals(
aggregations.get(5).toAggregationData(),
Expand Down Expand Up @@ -181,8 +180,6 @@ public void testAdd_DistributionWithExemplarAttachments() {
// 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())
Expand Down Expand Up @@ -258,13 +255,12 @@ public void testCombine_Distribution() {
MutableDistribution combined = MutableDistribution.create(BUCKET_BOUNDARIES);
combined.combine(distribution1, 1.0); // distribution1 will be combined
combined.combine(distribution2, 0.6); // distribution2 will be ignored
verifyMutableDistribution(combined, 0, 2, -5, 5, 50.0, new long[] {0, 1, 1, 0}, TOLERANCE);
verifyMutableDistribution(combined, 0, 2, -5, 5, 50.0, new long[] {2, 0}, TOLERANCE);

combined.combine(distribution2, 1.0); // distribution2 will be combined
verifyMutableDistribution(combined, 7.5, 4, -5, 20, 325.0, new long[] {0, 1, 1, 2}, TOLERANCE);

verifyMutableDistribution(combined, 7.5, 4, -5, 20, 325.0, new long[] {2, 2}, TOLERANCE);
combined.combine(distribution3, 1.0); // distribution3 will be combined
verifyMutableDistribution(combined, 0, 8, -20, 20, 1500.0, new long[] {2, 2, 1, 3}, TOLERANCE);
verifyMutableDistribution(combined, 0, 8, -20, 20, 1500.0, new long[] {5, 3}, TOLERANCE);
}

@Test
Expand All @@ -281,7 +277,7 @@ public void mutableAggregation_ToAggregationData() {
Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY,
0,
Arrays.asList(0L, 0L, 0L, 0L)));
Arrays.asList(0L, 0L)));
assertThat(MutableLastValueDouble.create().toAggregationData())
.isEqualTo(LastValueDataDouble.create(Double.NaN));
assertThat(MutableLastValueLong.create().toAggregationData())
Expand All @@ -299,8 +295,6 @@ public void mutableAggregation_ToPoint() {
assertThat(MutableMean.create().toPoint(TIMESTAMP))
.isEqualTo(Point.create(Value.doubleValue(0), TIMESTAMP));

thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("bucket boundary should be > 0");
assertThat(MutableDistribution.create(BUCKET_BOUNDARIES).toPoint(TIMESTAMP))
.isEqualTo(
Point.create(
Expand All @@ -310,11 +304,7 @@ public void mutableAggregation_ToPoint() {
0,
0,
BucketOptions.explicitOptions(BUCKET_BOUNDARIES.getBoundaries()),
Arrays.asList(
Bucket.create(0),
Bucket.create(0),
Bucket.create(0),
Bucket.create(0)))),
Arrays.asList(Bucket.create(0), Bucket.create(0)))),
TIMESTAMP));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,6 @@ public void createMutableAggregation() {
assertThat(mutableDistribution.getMin()).isPositiveInfinity();
assertThat(mutableDistribution.getMax()).isNegativeInfinity();
assertThat(mutableDistribution.getSumOfSquaredDeviations()).isWithin(EPSILON).of(0);
assertThat(mutableDistribution.getBucketCounts()).isEqualTo(new long[4]);
assertThat(mutableDistribution.getBucketCounts()).isEqualTo(new long[2]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,6 @@ public void record_WithAttachments_Distribution() {
(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();
Expand Down Expand Up @@ -209,7 +207,7 @@ public void record_WithAttachments_Count() {
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);
assertThat(countData.getCount()).isEqualTo(2L);
}

private void recordWithAttachments() {
Expand Down
Loading