From d93ffa390dbdc6d82e04a191493f0d70867644a6 Mon Sep 17 00:00:00 2001 From: Bogdan Cristian Drutu Date: Wed, 12 Feb 2020 10:25:36 -0800 Subject: [PATCH] Bound registry work in progress Signed-off-by: Bogdan Cristian Drutu --- .../sdk/metrics/AbstractBoundInstrument.java | 6 +- .../sdk/metrics/AbstractInstrument.java | 37 ++++++ .../AbstractInstrumentWithBinding.java | 112 ++++++++++++++++++ .../sdk/metrics/AggregatorMap.java | 110 +++++++++++++++++ .../sdk/metrics/AggregatorView.java | 87 ++++++++++++++ .../sdk/metrics/DoubleCounterSdk.java | 17 ++- .../sdk/metrics/DoubleMeasureSdk.java | 17 ++- .../sdk/metrics/LongCounterSdk.java | 17 ++- .../sdk/metrics/LongMeasureSdk.java | 17 ++- .../sdk/metrics/ViewManager.java | 87 ++++++++++++++ .../opentelemetry/sdk/metrics/view/View.java | 98 +++++++++++++++ .../sdk/metrics/DoubleCounterSdkTest.java | 22 +++- .../sdk/metrics/DoubleMeasureSdkTest.java | 8 +- .../sdk/metrics/LongCounterSdkTest.java | 7 +- .../sdk/metrics/LongMeasureSdkTest.java | 7 +- 15 files changed, 613 insertions(+), 36 deletions(-) create mode 100644 sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractInstrumentWithBinding.java create mode 100644 sdk/src/main/java/io/opentelemetry/sdk/metrics/AggregatorMap.java create mode 100644 sdk/src/main/java/io/opentelemetry/sdk/metrics/AggregatorView.java create mode 100644 sdk/src/main/java/io/opentelemetry/sdk/metrics/ViewManager.java create mode 100644 sdk/src/main/java/io/opentelemetry/sdk/metrics/view/View.java diff --git a/sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractBoundInstrument.java b/sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractBoundInstrument.java index c079bafe0bc..f96ac115215 100644 --- a/sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractBoundInstrument.java +++ b/sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractBoundInstrument.java @@ -35,8 +35,8 @@ abstract class AbstractBoundInstrument implements BoundInstrument { private final Aggregator aggregator; AbstractBoundInstrument(Aggregator aggregator) { - this.aggregator = aggregator; this.refCountMapped = new AtomicLong(0); + this.aggregator = aggregator; } /** @@ -78,4 +78,8 @@ final void recordLong(long value) { final void recordDouble(double value) { aggregator.recordDouble(value); } + + final Aggregator getAggregator() { + return aggregator; + } } diff --git a/sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractInstrument.java b/sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractInstrument.java index c1e8a4ff261..0e266b9f12d 100644 --- a/sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractInstrument.java +++ b/sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractInstrument.java @@ -20,8 +20,11 @@ import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; import io.opentelemetry.sdk.metrics.common.InstrumentType; import io.opentelemetry.sdk.metrics.common.InstrumentValueType; +import io.opentelemetry.sdk.metrics.view.Aggregations; +import io.opentelemetry.sdk.metrics.view.View; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; abstract class AbstractInstrument implements Instrument { @@ -32,6 +35,7 @@ abstract class AbstractInstrument implements Instrument { private final List labelKeys; private final MeterSharedState meterSharedState; private final InstrumentationLibraryInfo instrumentationLibraryInfo; + private final ViewManager viewManager; // All arguments cannot be null because they are checked in the abstract builder classes. AbstractInstrument( @@ -51,6 +55,16 @@ abstract class AbstractInstrument implements Instrument { this.labelKeys = labelKeys; this.meterSharedState = meterSharedState; this.instrumentationLibraryInfo = instrumentationLibraryInfo; + // TODO: Allow to install views from config instead of always installing the default View. + viewManager = + new ViewManager( + getDefaultView(name, description, constantLabels, instrumentType), + unit, + constantLabels, + instrumentType, + instrumentValueType, + meterSharedState, + instrumentationLibraryInfo); } final String getName() { @@ -81,6 +95,10 @@ final InstrumentationLibraryInfo getInstrumentationLibraryInfo() { return instrumentationLibraryInfo; } + final ViewManager getViewManager() { + return viewManager; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -120,4 +138,23 @@ static InstrumentType getMeasureInstrumentType(boolean absolute) { static InstrumentType getObserverInstrumentType(boolean monotonic) { return monotonic ? InstrumentType.OBSERVER_MONOTONIC : InstrumentType.OBSERVER_NON_MONOTONIC; } + + @Nullable + private static View getDefaultView( + String name, + String description, + Map constantLabels, + InstrumentType instrumentType) { + switch (instrumentType) { + case COUNTER_MONOTONIC: + case COUNTER_NON_MONOTONIC: + return View.create(name, description, Aggregations.sum(), constantLabels.keySet()); + case MEASURE_ABSOLUTE: + case MEASURE_NON_ABSOLUTE: + case OBSERVER_MONOTONIC: + case OBSERVER_NON_MONOTONIC: + // TODO: Add default views for every instrument type. + } + return null; + } } diff --git a/sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractInstrumentWithBinding.java b/sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractInstrumentWithBinding.java new file mode 100644 index 00000000000..140d1a9f3d8 --- /dev/null +++ b/sdk/src/main/java/io/opentelemetry/sdk/metrics/AbstractInstrumentWithBinding.java @@ -0,0 +1,112 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opentelemetry.sdk.metrics; + +import io.opentelemetry.metrics.LabelSet; +import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; +import io.opentelemetry.sdk.metrics.common.InstrumentType; +import io.opentelemetry.sdk.metrics.common.InstrumentValueType; +import io.opentelemetry.sdk.metrics.data.MetricData; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +abstract class AbstractInstrumentWithBinding + extends AbstractInstrument { + private final ConcurrentHashMap boundLabels; + private final ReentrantLock collectLock; + private long collectionStartTime; + + AbstractInstrumentWithBinding( + String name, + String description, + String unit, + Map constantLabels, + List labelKeys, + InstrumentType instrumentType, + InstrumentValueType instrumentValueType, + MeterSharedState meterSharedState, + InstrumentationLibraryInfo instrumentationLibraryInfo) { + super( + name, + description, + unit, + constantLabels, + labelKeys, + instrumentType, + instrumentValueType, + meterSharedState, + instrumentationLibraryInfo); + boundLabels = new ConcurrentHashMap<>(); + collectLock = new ReentrantLock(); + collectionStartTime = getMeterSharedState().getClock().now(); + } + + // Cannot make this "bind" because of a Java problem if we make this class also implement the + // InstrumentWithBinding then the subclass will fail to compile because of different "bind" + // signature. This is a good trade-off. + final B bindInternal(LabelSet labelSet) { + B binding = boundLabels.get(labelSet); + if (binding != null && binding.bind()) { + // At this moment it is guaranteed that the Bound is in the map and will not be removed. + return binding; + } + + // Missing entry or no longer mapped, try to add a new entry. + binding = newBinding(); + while (true) { + B oldBound = boundLabels.putIfAbsent(labelSet, binding); + if (oldBound != null) { + if (oldBound.bind()) { + // At this moment it is guaranteed that the Bound is in the map and will not be removed. + return oldBound; + } + // Try to remove the oldBound. This will race with the collect method, but only one will + // succeed. + boundLabels.remove(labelSet, oldBound); + continue; + } + return binding; + } + } + + /** + * Collects records from all the entries (labelSet, Bound) that changed since the last collect() + * call. + */ + final List collect() { + AggregatorMap aggregatorMap = getViewManager().startCollection(collectionStartTime); + collectionStartTime = getMeterSharedState().getClock().now(); + collectLock.lock(); + try { + for (Map.Entry entry : boundLabels.entrySet()) { + if (entry.getValue().tryUnmap()) { + // If able to unmap then remove the record from the current Map. This can race with the + // acquire but because we requested a specific value only one will succeed. + boundLabels.remove(entry.getKey(), entry.getValue()); + } + aggregatorMap.collect(entry.getKey(), entry.getValue().getAggregator()); + } + } finally { + collectLock.unlock(); + } + return aggregatorMap.stopCollection(collectionStartTime); + } + + abstract B newBinding(); +} diff --git a/sdk/src/main/java/io/opentelemetry/sdk/metrics/AggregatorMap.java b/sdk/src/main/java/io/opentelemetry/sdk/metrics/AggregatorMap.java new file mode 100644 index 00000000000..8d841ca78eb --- /dev/null +++ b/sdk/src/main/java/io/opentelemetry/sdk/metrics/AggregatorMap.java @@ -0,0 +1,110 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opentelemetry.sdk.metrics; + +import io.opentelemetry.metrics.LabelSet; +import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; +import io.opentelemetry.sdk.metrics.aggregator.Aggregator; +import io.opentelemetry.sdk.metrics.aggregator.AggregatorFactory; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.data.MetricData.Descriptor; +import io.opentelemetry.sdk.metrics.data.MetricData.Point; +import io.opentelemetry.sdk.resources.Resource; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +abstract class AggregatorMap { + static AggregatorMap getNoop() { + return Noop.INSTANCE; + } + + static AggregatorMap getAllLabels( + Descriptor descriptor, + Resource resource, + InstrumentationLibraryInfo instrumentationLibraryInfo, + AggregatorFactory aggregatorFactory, + long startEpochNanos) { + return new AllLabels( + descriptor, resource, instrumentationLibraryInfo, aggregatorFactory, startEpochNanos); + } + + abstract void collect(LabelSet labelSet, Aggregator aggregator); + + abstract List stopCollection(long epochNanos); + + private static final class Noop extends AggregatorMap { + private static final AggregatorMap INSTANCE = new Noop(); + + @Override + void collect(LabelSet labelSet, Aggregator aggregator) { + // Noop. + } + + @Override + List stopCollection(long epochNanos) { + return Collections.emptyList(); + } + } + + private static final class AllLabels extends AggregatorMap { + private final Descriptor descriptor; + private final Resource resource; + private final InstrumentationLibraryInfo instrumentationLibraryInfo; + private final AggregatorFactory aggregatorFactory; + private final Map, Aggregator> aggregatorMap; + private final long startEpochNanos; + + private AllLabels( + Descriptor descriptor, + Resource resource, + InstrumentationLibraryInfo instrumentationLibraryInfo, + AggregatorFactory aggregatorFactory, + long startEpochNanos) { + this.descriptor = descriptor; + this.resource = resource; + this.instrumentationLibraryInfo = instrumentationLibraryInfo; + this.aggregatorFactory = aggregatorFactory; + this.startEpochNanos = startEpochNanos; + this.aggregatorMap = new HashMap<>(); + } + + @Override + final void collect(LabelSet labelSet, Aggregator aggregator) { + // TODO: Add support to reduce labels. + Map labels = ((LabelSetSdk) labelSet).getLabels(); + Aggregator currentAggregator = aggregatorMap.get(labels); + if (currentAggregator == null) { + currentAggregator = aggregatorFactory.getAggregator(); + aggregatorMap.put(labels, currentAggregator); + } + aggregator.mergeToAndReset(currentAggregator); + } + + @Override + final List stopCollection(long epochNanos) { + List points = new ArrayList<>(aggregatorMap.size()); + for (Map.Entry, Aggregator> entry : aggregatorMap.entrySet()) { + points.add(entry.getValue().toPoint(startEpochNanos, epochNanos, entry.getKey())); + } + return Collections.singletonList( + MetricData.create(descriptor, resource, instrumentationLibraryInfo, points)); + } + } +} diff --git a/sdk/src/main/java/io/opentelemetry/sdk/metrics/AggregatorView.java b/sdk/src/main/java/io/opentelemetry/sdk/metrics/AggregatorView.java new file mode 100644 index 00000000000..a54139630ea --- /dev/null +++ b/sdk/src/main/java/io/opentelemetry/sdk/metrics/AggregatorView.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opentelemetry.sdk.metrics; + +import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; +import io.opentelemetry.sdk.metrics.aggregator.Aggregator; +import io.opentelemetry.sdk.metrics.aggregator.AggregatorFactory; +import io.opentelemetry.sdk.metrics.aggregator.NoopAggregator; +import io.opentelemetry.sdk.metrics.data.MetricData.Descriptor; +import io.opentelemetry.sdk.resources.Resource; + +abstract class AggregatorView { + + static AggregatorView getNoop() { + return Noop.INSTANCE; + } + + static AggregatorView getCumulativeAllLabels( + Descriptor descriptor, + Resource resource, + InstrumentationLibraryInfo instrumentationLibraryInfo, + AggregatorFactory aggregatorFactory, + long epochNanos) { + return new CumulativeAllLabels( + descriptor, resource, instrumentationLibraryInfo, aggregatorFactory, epochNanos); + } + + abstract Aggregator getAggregator(); + + // This may return the same object between collections in case of Cumulative aggregators. + abstract AggregatorMap getAggregatorMap(long startEpochNanos); + + private static final class Noop extends AggregatorView { + private static final AggregatorView INSTANCE = new Noop(); + + @Override + Aggregator getAggregator() { + return NoopAggregator.getFactory().getAggregator(); + } + + @Override + AggregatorMap getAggregatorMap(long startEpochNanos) { + return AggregatorMap.getNoop(); + } + } + + private static final class CumulativeAllLabels extends AggregatorView { + private final AggregatorFactory aggregatorFactory; + private final AggregatorMap aggregatorMap; + + CumulativeAllLabels( + Descriptor descriptor, + Resource resource, + InstrumentationLibraryInfo instrumentationLibraryInfo, + AggregatorFactory aggregatorFactory, + long startEpochNanos) { + this.aggregatorFactory = aggregatorFactory; + this.aggregatorMap = + AggregatorMap.getAllLabels( + descriptor, resource, instrumentationLibraryInfo, aggregatorFactory, startEpochNanos); + } + + @Override + Aggregator getAggregator() { + return aggregatorFactory.getAggregator(); + } + + @Override + AggregatorMap getAggregatorMap(long startEpochNanos) { + return aggregatorMap; + } + } +} diff --git a/sdk/src/main/java/io/opentelemetry/sdk/metrics/DoubleCounterSdk.java b/sdk/src/main/java/io/opentelemetry/sdk/metrics/DoubleCounterSdk.java index 6d8d8276a80..b80ff7a6e77 100644 --- a/sdk/src/main/java/io/opentelemetry/sdk/metrics/DoubleCounterSdk.java +++ b/sdk/src/main/java/io/opentelemetry/sdk/metrics/DoubleCounterSdk.java @@ -19,11 +19,13 @@ import io.opentelemetry.metrics.DoubleCounter; import io.opentelemetry.metrics.LabelSet; import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; +import io.opentelemetry.sdk.metrics.DoubleCounterSdk.BoundInstrument; import io.opentelemetry.sdk.metrics.common.InstrumentValueType; import java.util.List; import java.util.Map; -final class DoubleCounterSdk extends AbstractInstrument implements DoubleCounter { +final class DoubleCounterSdk extends AbstractInstrumentWithBinding + implements DoubleCounter { private final boolean monotonic; @@ -58,7 +60,12 @@ public void add(double delta, LabelSet labelSet) { @Override public BoundInstrument bind(LabelSet labelSet) { - return new BoundInstrument(monotonic); + return bindInternal(labelSet); + } + + @Override + BoundInstrument newBinding() { + return new BoundInstrument(monotonic, getViewManager()); } @Override @@ -89,8 +96,8 @@ static final class BoundInstrument extends AbstractBoundInstrument implements Bo private final boolean monotonic; - BoundInstrument(boolean monotonic) { - super(null); + BoundInstrument(boolean monotonic, ViewManager viewManager) { + super(viewManager.getAggregator()); this.monotonic = monotonic; } @@ -99,7 +106,7 @@ public void add(double delta) { if (monotonic && delta < 0) { throw new IllegalArgumentException("monotonic counters can only increase"); } - // TODO: pass through to an aggregator/accumulator + recordDouble(delta); } } diff --git a/sdk/src/main/java/io/opentelemetry/sdk/metrics/DoubleMeasureSdk.java b/sdk/src/main/java/io/opentelemetry/sdk/metrics/DoubleMeasureSdk.java index 1a801797d70..ed6a2cd8274 100644 --- a/sdk/src/main/java/io/opentelemetry/sdk/metrics/DoubleMeasureSdk.java +++ b/sdk/src/main/java/io/opentelemetry/sdk/metrics/DoubleMeasureSdk.java @@ -19,11 +19,13 @@ import io.opentelemetry.metrics.DoubleMeasure; import io.opentelemetry.metrics.LabelSet; import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; +import io.opentelemetry.sdk.metrics.DoubleMeasureSdk.BoundInstrument; import io.opentelemetry.sdk.metrics.common.InstrumentValueType; import java.util.List; import java.util.Map; -final class DoubleMeasureSdk extends AbstractInstrument implements DoubleMeasure { +final class DoubleMeasureSdk extends AbstractInstrumentWithBinding + implements DoubleMeasure { private final boolean absolute; @@ -58,7 +60,12 @@ public void record(double value, LabelSet labelSet) { @Override public BoundInstrument bind(LabelSet labelSet) { - return new BoundInstrument(this.absolute); + return bindInternal(labelSet); + } + + @Override + BoundInstrument newBinding() { + return new BoundInstrument(this.absolute, getViewManager()); } @Override @@ -89,8 +96,8 @@ static final class BoundInstrument extends AbstractBoundInstrument implements Bo private final boolean absolute; - BoundInstrument(boolean absolute) { - super(null); + BoundInstrument(boolean absolute, ViewManager viewManager) { + super(viewManager.getAggregator()); this.absolute = absolute; } @@ -99,7 +106,7 @@ public void record(double value) { if (this.absolute && value < 0) { throw new IllegalArgumentException("absolute measure can only record positive values"); } - // TODO: pass through to an aggregator/accumulator + recordDouble(value); } } diff --git a/sdk/src/main/java/io/opentelemetry/sdk/metrics/LongCounterSdk.java b/sdk/src/main/java/io/opentelemetry/sdk/metrics/LongCounterSdk.java index 6668e5da354..350e2599c37 100644 --- a/sdk/src/main/java/io/opentelemetry/sdk/metrics/LongCounterSdk.java +++ b/sdk/src/main/java/io/opentelemetry/sdk/metrics/LongCounterSdk.java @@ -19,11 +19,13 @@ import io.opentelemetry.metrics.LabelSet; import io.opentelemetry.metrics.LongCounter; import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; +import io.opentelemetry.sdk.metrics.LongCounterSdk.BoundInstrument; import io.opentelemetry.sdk.metrics.common.InstrumentValueType; import java.util.List; import java.util.Map; -final class LongCounterSdk extends AbstractInstrument implements LongCounter { +final class LongCounterSdk extends AbstractInstrumentWithBinding + implements LongCounter { private final boolean monotonic; @@ -58,7 +60,12 @@ public void add(long delta, LabelSet labelSet) { @Override public BoundInstrument bind(LabelSet labelSet) { - return new BoundInstrument(monotonic); + return bindInternal(labelSet); + } + + @Override + BoundInstrument newBinding() { + return new BoundInstrument(monotonic, getViewManager()); } @Override @@ -89,8 +96,8 @@ static final class BoundInstrument extends AbstractBoundInstrument implements Bo private final boolean monotonic; - BoundInstrument(boolean monotonic) { - super(null); + BoundInstrument(boolean monotonic, ViewManager viewManager) { + super(viewManager.getAggregator()); this.monotonic = monotonic; } @@ -99,7 +106,7 @@ public void add(long delta) { if (monotonic && delta < 0) { throw new IllegalArgumentException("monotonic counters can only increase"); } - // TODO: pass through to an aggregator/accumulator + recordLong(delta); } } diff --git a/sdk/src/main/java/io/opentelemetry/sdk/metrics/LongMeasureSdk.java b/sdk/src/main/java/io/opentelemetry/sdk/metrics/LongMeasureSdk.java index 7a47b104bee..38bdf2e06ea 100644 --- a/sdk/src/main/java/io/opentelemetry/sdk/metrics/LongMeasureSdk.java +++ b/sdk/src/main/java/io/opentelemetry/sdk/metrics/LongMeasureSdk.java @@ -19,11 +19,13 @@ import io.opentelemetry.metrics.LabelSet; import io.opentelemetry.metrics.LongMeasure; import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; +import io.opentelemetry.sdk.metrics.LongMeasureSdk.BoundInstrument; import io.opentelemetry.sdk.metrics.common.InstrumentValueType; import java.util.List; import java.util.Map; -final class LongMeasureSdk extends AbstractInstrument implements LongMeasure { +final class LongMeasureSdk extends AbstractInstrumentWithBinding + implements LongMeasure { private final boolean absolute; @@ -58,7 +60,12 @@ public void record(long value, LabelSet labelSet) { @Override public BoundInstrument bind(LabelSet labelSet) { - return new BoundInstrument(this.absolute); + return bindInternal(labelSet); + } + + @Override + BoundInstrument newBinding() { + return new BoundInstrument(this.absolute, getViewManager()); } @Override @@ -89,8 +96,8 @@ static final class BoundInstrument extends AbstractBoundInstrument implements Bo private final boolean absolute; - BoundInstrument(boolean absolute) { - super(null); + BoundInstrument(boolean absolute, ViewManager viewManager) { + super(viewManager.getAggregator()); this.absolute = absolute; } @@ -99,7 +106,7 @@ public void record(long value) { if (this.absolute && value < 0) { throw new IllegalArgumentException("absolute measure can only record positive values"); } - // TODO: pass through to an aggregator/accumulator + recordLong(value); } } diff --git a/sdk/src/main/java/io/opentelemetry/sdk/metrics/ViewManager.java b/sdk/src/main/java/io/opentelemetry/sdk/metrics/ViewManager.java new file mode 100644 index 00000000000..0953397ec3e --- /dev/null +++ b/sdk/src/main/java/io/opentelemetry/sdk/metrics/ViewManager.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opentelemetry.sdk.metrics; + +import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; +import io.opentelemetry.sdk.metrics.aggregator.Aggregator; +import io.opentelemetry.sdk.metrics.common.InstrumentType; +import io.opentelemetry.sdk.metrics.common.InstrumentValueType; +import io.opentelemetry.sdk.metrics.data.MetricData.Descriptor; +import io.opentelemetry.sdk.metrics.view.View; +import java.util.Map; +import javax.annotation.Nullable; + +// The current implementation allows to set only one View. This is good for the moment to support +// default aggregation for all the instruments. +// +// TODO: Future work: +// * Add support for multiple views in the same time. +// * Consider to add support to change views at runtime, this may be dangerous. +final class ViewManager { + private final AggregatorView aggregatorView; + + ViewManager( + @Nullable View view, + String unit, + Map constantLabels, + InstrumentType instrumentType, + InstrumentValueType instrumentValueType, + MeterSharedState sharedState, + InstrumentationLibraryInfo instrumentationLibraryInfo) { + if (view != null) { + this.aggregatorView = + AggregatorView.getCumulativeAllLabels( + toDescriptor(view, unit, constantLabels, instrumentType, instrumentValueType), + sharedState.getResource(), + instrumentationLibraryInfo, + view.getAggregation().getAggregatorFactory(instrumentValueType), + sharedState.getClock().now()); + } else { + this.aggregatorView = AggregatorView.getNoop(); + } + } + + // Caller needs to call methods in the following order (AggregationMap is not thread safe and may + // be shared across multiple collections, so users need to make sure that only one collection + // happen at a time): + // * AggregatorMap aggregatorMap = startCollection(); + // * aggregatorMap.collect(); // May be called multiple times. + // * startCollection() + AggregatorMap startCollection(long startEpochNanos) { + return aggregatorView.getAggregatorMap(startEpochNanos); + } + + Aggregator getAggregator() { + return aggregatorView.getAggregator(); + } + + // This should change the signature when we add View to be: + // static Descriptor toDescriptor(View, InstrumentType, InstrumentValueType); + private static Descriptor toDescriptor( + View view, + String unit, + Map constantLabels, + InstrumentType instrumentType, + InstrumentValueType instrumentValueType) { + return Descriptor.create( + view.getName(), + view.getDescription(), + unit, + view.getAggregation().getDescriptorType(instrumentType, instrumentValueType), + constantLabels); + } +} diff --git a/sdk/src/main/java/io/opentelemetry/sdk/metrics/view/View.java b/sdk/src/main/java/io/opentelemetry/sdk/metrics/view/View.java new file mode 100644 index 00000000000..0a715b6f191 --- /dev/null +++ b/sdk/src/main/java/io/opentelemetry/sdk/metrics/view/View.java @@ -0,0 +1,98 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opentelemetry.sdk.metrics.view; + +import com.google.auto.value.AutoValue; +import io.opentelemetry.internal.StringUtils; +import io.opentelemetry.internal.Utils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import javax.annotation.concurrent.Immutable; + +/** + * A View specifies an aggregation that can be applied to an {@code Instrument}. + * + * @since 0.8 + */ +@Immutable +@AutoValue +public abstract class View { + private static final int NAME_MAX_LENGTH = 255; + + View() {} + + /** + * Name of view. Must be unique. + * + * @since 0.8 + */ + public abstract String getName(); + + /** + * More detailed description, for documentation purposes. + * + * @since 0.8 + */ + public abstract String getDescription(); + + /** + * The {@link Aggregation} associated with this {@link View}. + * + * @since 0.8 + */ + public abstract Aggregation getAggregation(); + + /** + * ConstantColumns (a.k.a keys from the ConstantLabels) to match with the associated {@code + * Instrument}. + * + *

This is similar to do a GROUPBY on ConstantLabels defined in the {@code Instrument}. Columns + * must be unique. + * + * @since 0.8 + */ + public abstract List getConstantColumns(); + + /** + * Constructs a new {@link View}. + * + * @param name the name of view. Must be unique. + * @param description the description of view. + * @param aggregation the basic {@link Aggregation} that this view will support. + * @return a new {@link View}. + * @since 0.13 + */ + public static View create( + String name, + String description, + Aggregation aggregation, + Collection constantColumns) { + Utils.checkArgument( + new HashSet<>(constantColumns).size() == constantColumns.size(), + "ConstantColumns have duplicate."); + List constantKeys = new ArrayList<>(constantColumns); + Collections.sort(constantKeys); + Utils.checkArgument( + StringUtils.isPrintableString(name) && name.length() <= NAME_MAX_LENGTH, + "Name should be a ASCII string with a length no greater than 255 characters."); + return new AutoValue_View( + name, description, aggregation, Collections.unmodifiableList(constantKeys)); + } +} diff --git a/sdk/src/test/java/io/opentelemetry/sdk/metrics/DoubleCounterSdkTest.java b/sdk/src/test/java/io/opentelemetry/sdk/metrics/DoubleCounterSdkTest.java index 498b1a9eb46..3845130a551 100644 --- a/sdk/src/test/java/io/opentelemetry/sdk/metrics/DoubleCounterSdkTest.java +++ b/sdk/src/test/java/io/opentelemetry/sdk/metrics/DoubleCounterSdkTest.java @@ -16,11 +16,15 @@ package io.opentelemetry.sdk.metrics; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.collect.ImmutableMap; import io.opentelemetry.metrics.DoubleCounter; import io.opentelemetry.metrics.DoubleCounter.BoundDoubleCounter; import io.opentelemetry.metrics.LabelSet; +import io.opentelemetry.sdk.metrics.data.MetricData; import java.util.Collections; +import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -53,12 +57,20 @@ public void testDoubleCounter() { BoundDoubleCounter boundDoubleCounter = doubleCounter.bind(labelSet); boundDoubleCounter.add(334.999d); - // TODO: Uncomment. - // BoundDoubleCounter duplicateBoundCounter = doubleCounter.bind(testSdk.createLabelSet("K", - // "v")); - // assertThat(duplicateBoundCounter).isEqualTo(boundDoubleCounter); + BoundDoubleCounter duplicateBoundCounter = doubleCounter.bind(testSdk.createLabelSet("K", "v")); + assertThat(duplicateBoundCounter).isEqualTo(boundDoubleCounter); + duplicateBoundCounter.add(123.007d); + + doubleCounter.add(13.1001, testSdk.createLabelSet()); + + assertThat(doubleCounter).isInstanceOf(DoubleCounterSdk.class); + List metricDataList = ((DoubleCounterSdk) doubleCounter).collect(); + assertThat(metricDataList).hasSize(1); - // todo: verify that this has done something, when it has been done. + MetricData metricData = metricDataList.get(0); + assertThat(metricData.getInstrumentationLibraryInfo()) + .isEqualTo(testSdk.getInstrumentationLibraryInfo()); + assertThat(metricData.getPoints()).hasSize(2); boundDoubleCounter.unbind(); } diff --git a/sdk/src/test/java/io/opentelemetry/sdk/metrics/DoubleMeasureSdkTest.java b/sdk/src/test/java/io/opentelemetry/sdk/metrics/DoubleMeasureSdkTest.java index 036d67cc4ab..3e3d203e4ec 100644 --- a/sdk/src/test/java/io/opentelemetry/sdk/metrics/DoubleMeasureSdkTest.java +++ b/sdk/src/test/java/io/opentelemetry/sdk/metrics/DoubleMeasureSdkTest.java @@ -16,6 +16,8 @@ package io.opentelemetry.sdk.metrics; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.collect.ImmutableMap; import io.opentelemetry.metrics.DoubleMeasure; import io.opentelemetry.metrics.DoubleMeasure.BoundDoubleMeasure; @@ -53,10 +55,8 @@ public void testDoubleMeasure() { BoundDoubleMeasure boundDoubleMeasure = doubleMeasure.bind(labelSet); boundDoubleMeasure.record(334.999d); - // TODO: Uncomment. - // BoundDoubleMeasure duplicateBoundMeasure = doubleMeasure.bind(testSdk.createLabelSet("K", - // "v")); - // assertThat(duplicateBoundMeasure).isEqualTo(boundDoubleMeasure); + BoundDoubleMeasure duplicateBoundMeasure = doubleMeasure.bind(testSdk.createLabelSet("K", "v")); + assertThat(duplicateBoundMeasure).isEqualTo(boundDoubleMeasure); // todo: verify that this has done something, when it has been done. boundDoubleMeasure.unbind(); diff --git a/sdk/src/test/java/io/opentelemetry/sdk/metrics/LongCounterSdkTest.java b/sdk/src/test/java/io/opentelemetry/sdk/metrics/LongCounterSdkTest.java index 99234e5ba60..441df750da4 100644 --- a/sdk/src/test/java/io/opentelemetry/sdk/metrics/LongCounterSdkTest.java +++ b/sdk/src/test/java/io/opentelemetry/sdk/metrics/LongCounterSdkTest.java @@ -16,6 +16,8 @@ package io.opentelemetry.sdk.metrics; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.collect.ImmutableMap; import io.opentelemetry.metrics.LabelSet; import io.opentelemetry.metrics.LongCounter; @@ -53,9 +55,8 @@ public void testLongCounter() { BoundLongCounter boundLongCounter = longCounter.bind(labelSet); boundLongCounter.add(334); - // TODO: Uncomment. - // BoundLongCounter duplicateBoundCounter = longCounter.bind(testSdk.createLabelSet("K", "v")); - // assertThat(duplicateBoundCounter).isEqualTo(boundLongCounter); + BoundLongCounter duplicateBoundCounter = longCounter.bind(testSdk.createLabelSet("K", "v")); + assertThat(duplicateBoundCounter).isEqualTo(boundLongCounter); // todo: verify that this has done something, when it has been done. boundLongCounter.unbind(); diff --git a/sdk/src/test/java/io/opentelemetry/sdk/metrics/LongMeasureSdkTest.java b/sdk/src/test/java/io/opentelemetry/sdk/metrics/LongMeasureSdkTest.java index d5b5cac4b5a..eec132e565e 100644 --- a/sdk/src/test/java/io/opentelemetry/sdk/metrics/LongMeasureSdkTest.java +++ b/sdk/src/test/java/io/opentelemetry/sdk/metrics/LongMeasureSdkTest.java @@ -16,6 +16,8 @@ package io.opentelemetry.sdk.metrics; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.collect.ImmutableMap; import io.opentelemetry.metrics.LabelSet; import io.opentelemetry.metrics.LongMeasure; @@ -53,9 +55,8 @@ public void testLongMeasure() { BoundLongMeasure boundLongMeasure = longMeasure.bind(labelSet); boundLongMeasure.record(334); - // TODO: Uncomment. - // BoundLongMeasure duplicateBoundMeasure = longMeasure.bind(testSdk.createLabelSet("K", "v")); - // assertThat(duplicateBoundMeasure).isEqualTo(boundLongMeasure); + BoundLongMeasure duplicateBoundMeasure = longMeasure.bind(testSdk.createLabelSet("K", "v")); + assertThat(duplicateBoundMeasure).isEqualTo(boundLongMeasure); // todo: verify that this has done something, when it has been done. boundLongMeasure.unbind();