aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--RELEASING.md1
-rw-r--r--all/build.gradle2
-rw-r--r--build.gradle3
-rw-r--r--exporters/stats/prometheus/README.md58
-rw-r--r--exporters/stats/prometheus/build.gradle15
-rw-r--r--exporters/stats/prometheus/src/main/java/io/opencensus/exporter/stats/prometheus/PrometheusExportUtils.java208
-rw-r--r--exporters/stats/prometheus/src/main/java/io/opencensus/exporter/stats/prometheus/PrometheusStatsCollector.java89
-rw-r--r--exporters/stats/prometheus/src/test/java/io/opencensus/exporter/stats/prometheus/PrometheusExportUtilsTest.java296
-rw-r--r--exporters/stats/prometheus/src/test/java/io/opencensus/exporter/stats/prometheus/PrometheusStatsCollectorTest.java140
-rw-r--r--settings.gradle3
10 files changed, 815 insertions, 0 deletions
diff --git a/RELEASING.md b/RELEASING.md
index 2d42ed0b..675317e0 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -219,6 +219,7 @@ $ README_FILES=(
contrib/grpc_util/README.md
contrib/http_util/README.md
contrib/zpages/README.md
+ exporters/stats/prometheus/README.md
exporters/stats/signalfx/README.md
exporters/stats/stackdriver/README.md
exporters/trace/instana/README.md
diff --git a/all/build.gradle b/all/build.gradle
index 7ddfe177..3bc6c2b6 100644
--- a/all/build.gradle
+++ b/all/build.gradle
@@ -22,6 +22,7 @@ def subprojects = [
project(':opencensus-exporter-trace-zipkin'),
project(':opencensus-exporter-stats-signalfx'),
project(':opencensus-exporter-stats-stackdriver'),
+ project(':opencensus-exporter-stats-prometheus'),
]
// A subset of subprojects for which we want to publish javadoc.
@@ -38,6 +39,7 @@ def subprojects_javadoc = [
project(':opencensus-exporter-trace-zipkin'),
project(':opencensus-exporter-stats-signalfx'),
project(':opencensus-exporter-stats-stackdriver'),
+ project(':opencensus-exporter-stats-prometheus'),
]
for (subproject in rootProject.subprojects) {
diff --git a/build.gradle b/build.gradle
index a305e91a..66cc725a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -141,6 +141,7 @@ subprojects {
googleCloudVersion = '0.33.0-beta'
signalfxVersion = '0.0.39'
zipkinReporterVersion = '2.0.0'
+ prometheusVersion = '0.2.0'
libraries = [
auto_value: "com.google.auto.value:auto-value:${autoValueVersion}",
@@ -160,6 +161,7 @@ subprojects {
guava: "com.google.guava:guava:${guavaVersion}",
jsr305: "com.google.code.findbugs:jsr305:${findBugsVersion}",
signalfx_java: "com.signalfx.public:signalfx-java:${signalfxVersion}",
+ prometheus_simpleclient: "io.prometheus:simpleclient:${prometheusVersion}",
// Test dependencies.
guava_testlib: "com.google.guava:guava-testlib:${guavaVersion}",
@@ -333,6 +335,7 @@ subprojects {
'opencensus-contrib-grpc-util',
'opencensus-contrib-http-util',
'opencensus-contrib-zpages',
+ 'opencensus-exporter-stats-prometheus',
'opencensus-exporter-stats-signalfx',
'opencensus-exporter-stats-stackdriver',
'opencensus-exporter-trace-instana',
diff --git a/exporters/stats/prometheus/README.md b/exporters/stats/prometheus/README.md
new file mode 100644
index 00000000..c506b077
--- /dev/null
+++ b/exporters/stats/prometheus/README.md
@@ -0,0 +1,58 @@
+# OpenCensus Prometheus Stats Exporter
+
+The *OpenCensus Prometheus Stats Exporter* is a stats exporter that exports data to
+Prometheus. [Prometheus](https://prometheus.io/) is an open-source systems monitoring and alerting
+toolkit originally built at [SoundCloud](https://soundcloud.com/).
+
+## Quickstart
+
+### Prerequisites
+
+To use this exporter, you need to install, configure and start Prometheus first. Follow the
+instructions [here](https://prometheus.io/docs/introduction/first_steps/).
+
+### Hello "Prometheus Stats"
+
+#### Add the dependencies to your project
+
+TODO
+
+For Maven add to your `pom.xml`:
+
+For Gradle add to your dependencies:
+
+#### Register the exporter
+
+```java
+public class MyMainClass {
+ public static void main(String[] args) {
+ // Creates a PrometheusStatsCollector and registers it to the default Prometheus registry.
+ PrometheusStatsCollector.createAndRegister();
+
+ // Uses a simple Prometheus HTTPServer to export metrics.
+ // You can use a Prometheus PushGateway instead, though that's discouraged by Prometheus:
+ // https://prometheus.io/docs/practices/pushing/#should-i-be-using-the-pushgateway.
+ io.prometheus.client.exporter.HTTPServer server =
+ new HTTPServer(/*host*/ "localhost", /*port*/ 9091, /*daemon*/ true);
+
+ // Your code here.
+ // ...
+ }
+}
+```
+
+In this example, you should be able to see all the OpenCensus Prometheus metrics by visiting
+localhost:9091/metrics. Every time when you visit localhost:9091/metrics, the metrics will be
+collected from OpenCensus library and refreshed.
+
+#### Exporting
+
+After collecting stats from OpenCensus, there are multiple options for exporting them.
+See [Exporting via HTTP](https://github.com/prometheus/client_java#http), [Exporting to a Pushgateway](https://github.com/prometheus/client_java#exporting-to-a-pushgateway)
+and [Bridges](https://github.com/prometheus/client_java#bridges).
+
+#### Java Versions
+
+Java 7 or above is required for using this exporter.
+
+## FAQ \ No newline at end of file
diff --git a/exporters/stats/prometheus/build.gradle b/exporters/stats/prometheus/build.gradle
new file mode 100644
index 00000000..27ca1d70
--- /dev/null
+++ b/exporters/stats/prometheus/build.gradle
@@ -0,0 +1,15 @@
+description = 'OpenCensus Stats Prometheus Exporter'
+
+[compileJava, compileTestJava].each() {
+ it.sourceCompatibility = 1.7
+ it.targetCompatibility = 1.7
+}
+
+dependencies {
+ compile project(':opencensus-api'),
+ libraries.prometheus_simpleclient
+
+ testCompile project(':opencensus-api')
+
+ signature "org.codehaus.mojo.signature:java17:+@signature"
+} \ No newline at end of file
diff --git a/exporters/stats/prometheus/src/main/java/io/opencensus/exporter/stats/prometheus/PrometheusExportUtils.java b/exporters/stats/prometheus/src/main/java/io/opencensus/exporter/stats/prometheus/PrometheusExportUtils.java
new file mode 100644
index 00000000..4bc18826
--- /dev/null
+++ b/exporters/stats/prometheus/src/main/java/io/opencensus/exporter/stats/prometheus/PrometheusExportUtils.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2018, OpenCensus 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.opencensus.exporter.stats.prometheus;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.stats.Aggregation;
+import io.opencensus.stats.Aggregation.Count;
+import io.opencensus.stats.Aggregation.Distribution;
+import io.opencensus.stats.Aggregation.Mean;
+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.MeanData;
+import io.opencensus.stats.AggregationData.SumDataDouble;
+import io.opencensus.stats.AggregationData.SumDataLong;
+import io.opencensus.stats.View;
+import io.opencensus.stats.View.AggregationWindow;
+import io.opencensus.stats.View.AggregationWindow.Cumulative;
+import io.opencensus.stats.ViewData;
+import io.opencensus.tags.TagKey;
+import io.opencensus.tags.TagValue;
+import io.prometheus.client.Collector;
+import io.prometheus.client.Collector.MetricFamilySamples;
+import io.prometheus.client.Collector.MetricFamilySamples.Sample;
+import io.prometheus.client.Collector.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map.Entry;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/**
+ * Util methods to convert OpenCensus Stats data models to Prometheus data models.
+ *
+ * <p>Each OpenCensus {@link View} will be converted to a Prometheus {@link MetricFamilySamples}
+ * with no {@link Sample}s, and is used for registering Prometheus {@code Metric}s. Only {@link
+ * Cumulative} views are supported. All views are under namespace "opencensus".
+ *
+ * <p>{@link Aggregation} will be converted to a corresponding Prometheus {@link Type}. {@link Sum}
+ * will be {@link Type#UNTYPED}, {@link Count} will be {@link Type#COUNTER}, {@link Mean} will be
+ * {@link Type#SUMMARY} and {@link Distribution} will be {@link Type#HISTOGRAM}. Please note we
+ * cannot set bucket boundaries for custom {@link Type#HISTOGRAM}.
+ *
+ * <p>Each OpenCensus {@link ViewData} will be converted to a Prometheus {@link
+ * MetricFamilySamples}, and each {@code Row} of the {@link ViewData} will be converted to
+ * Prometheus {@link Sample}s.
+ *
+ * <p>{@link SumDataDouble}, {@link SumDataLong} and {@link CountData} will be converted to a single
+ * {@link Sample}. {@link MeanData} will be converted to two {@link Sample}s sum and count. {@link
+ * DistributionData} will be converted to a list of {@link Sample}s that have the sum, count and
+ * histogram buckets.
+ *
+ * <p>{@link TagKey} and {@link TagValue} will be converted to Prometheus {@code LabelName} and
+ * {@code LabelValue}. {@code Null} {@link TagValue} will be converted to an empty string.
+ *
+ * <p>Please note that Prometheus Metric and Label name can only have alphanumeric characters and
+ * underscore. All other characters will be sanitized by underscores.
+ */
+final class PrometheusExportUtils {
+
+ @VisibleForTesting static final String OPENCENSUS_NAMESPACE = "opencensus";
+ @VisibleForTesting static final String OPENCENSUS_HELP_MSG = "Opencensus Prometheus metrics: ";
+ @VisibleForTesting static final String SAMPLE_SUFFIX_BUCKET = "_bucket";
+ @VisibleForTesting static final String SAMPLE_SUFFIX_COUNT = "_count";
+ @VisibleForTesting static final String SAMPLE_SUFFIX_SUM = "_sum";
+
+ // Converts a ViewData to a Prometheus MetricFamilySamples.
+ static MetricFamilySamples createMetricFamilySamples(ViewData viewData) {
+ View view = viewData.getView();
+ String name =
+ Collector.sanitizeMetricName(OPENCENSUS_NAMESPACE + '_' + view.getName().asString());
+ Type type = getType(view.getAggregation(), view.getWindow());
+ List<Sample> samples = Lists.newArrayList();
+ for (Entry<List<TagValue>, AggregationData> entry : viewData.getAggregationMap().entrySet()) {
+ samples.addAll(getSamples(name, view.getColumns(), entry.getKey(), entry.getValue()));
+ }
+ return new MetricFamilySamples(
+ name, type, OPENCENSUS_HELP_MSG + view.getDescription(), samples);
+ }
+
+ // Converts a View to a Prometheus MetricFamilySamples.
+ // Used only for Prometheus metric registry, should not contain any actual samples.
+ static MetricFamilySamples createDescribableMetricFamilySamples(View view) {
+ String name =
+ Collector.sanitizeMetricName(OPENCENSUS_NAMESPACE + '_' + view.getName().asString());
+ Type type = getType(view.getAggregation(), view.getWindow());
+ return new MetricFamilySamples(
+ name, type, OPENCENSUS_HELP_MSG + view.getDescription(), Collections.<Sample>emptyList());
+ }
+
+ @VisibleForTesting
+ static Type getType(Aggregation aggregation, AggregationWindow window) {
+ if (!(window instanceof Cumulative)) {
+ return Type.UNTYPED;
+ }
+ return aggregation.match(
+ Functions.returnConstant(Type.UNTYPED), // SUM
+ Functions.returnConstant(Type.COUNTER), // COUNT
+ Functions.returnConstant(Type.SUMMARY), // MEAN
+ Functions.returnConstant(Type.HISTOGRAM), // DISTRIBUTION
+ Functions.returnConstant(Type.UNTYPED));
+ }
+
+ @VisibleForTesting
+ static List<Sample> getSamples(
+ final String name,
+ List<TagKey> tagKeys,
+ List<TagValue> tagValues,
+ AggregationData aggregationData) {
+ Preconditions.checkArgument(
+ tagKeys.size() == tagValues.size(), "Tag keys and tag values have different sizes.");
+ final List<Sample> samples = Lists.newArrayList();
+ final List<String> labelNames = new ArrayList<String>(tagKeys.size());
+ final List<String> labelValues = new ArrayList<String>(tagValues.size());
+ for (TagKey tagKey : tagKeys) {
+ labelNames.add(Collector.sanitizeMetricName(tagKey.getName()));
+ }
+ for (TagValue tagValue : tagValues) {
+ String labelValue = tagValue == null ? "" : tagValue.asString();
+ labelValues.add(labelValue);
+ }
+
+ aggregationData.match(
+ new Function<SumDataDouble, Void>() {
+ @Override
+ public Void apply(SumDataDouble arg) {
+ samples.add(new Sample(name, labelNames, labelValues, arg.getSum()));
+ return null;
+ }
+ },
+ new Function<SumDataLong, Void>() {
+ @Override
+ public Void apply(SumDataLong arg) {
+ samples.add(new Sample(name, labelNames, labelValues, arg.getSum()));
+ return null;
+ }
+ },
+ new Function<CountData, Void>() {
+ @Override
+ public Void apply(CountData arg) {
+ samples.add(new Sample(name, labelNames, labelValues, arg.getCount()));
+ return null;
+ }
+ },
+ new Function<MeanData, Void>() {
+ @Override
+ public Void apply(MeanData arg) {
+ samples.add(
+ new MetricFamilySamples.Sample(
+ name + SAMPLE_SUFFIX_COUNT, labelNames, labelValues, arg.getCount()));
+ samples.add(
+ new MetricFamilySamples.Sample(
+ name + SAMPLE_SUFFIX_SUM,
+ labelNames,
+ labelValues,
+ arg.getCount() * arg.getMean()));
+ return null;
+ }
+ },
+ new Function<DistributionData, Void>() {
+ @Override
+ public Void apply(DistributionData arg) {
+ for (long bucketCount : arg.getBucketCounts()) {
+ samples.add(
+ new MetricFamilySamples.Sample(
+ name + SAMPLE_SUFFIX_BUCKET, labelNames, labelValues, bucketCount));
+ }
+ samples.add(
+ new MetricFamilySamples.Sample(
+ name + SAMPLE_SUFFIX_COUNT, labelNames, labelValues, arg.getCount()));
+ samples.add(
+ new MetricFamilySamples.Sample(
+ name + SAMPLE_SUFFIX_SUM,
+ labelNames,
+ labelValues,
+ arg.getCount() * arg.getMean()));
+ return null;
+ }
+ },
+ Functions.</*@Nullable*/ Void>throwAssertionError());
+
+ return samples;
+ }
+
+ private PrometheusExportUtils() {}
+}
diff --git a/exporters/stats/prometheus/src/main/java/io/opencensus/exporter/stats/prometheus/PrometheusStatsCollector.java b/exporters/stats/prometheus/src/main/java/io/opencensus/exporter/stats/prometheus/PrometheusStatsCollector.java
new file mode 100644
index 00000000..8fcefb0c
--- /dev/null
+++ b/exporters/stats/prometheus/src/main/java/io/opencensus/exporter/stats/prometheus/PrometheusStatsCollector.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018, OpenCensus 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.opencensus.exporter.stats.prometheus;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import io.opencensus.stats.Stats;
+import io.opencensus.stats.View;
+import io.opencensus.stats.ViewData;
+import io.opencensus.stats.ViewManager;
+import io.prometheus.client.Collector;
+import io.prometheus.client.CollectorRegistry;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * OpenCensus Stats {@link Collector} for Prometheus.
+ *
+ * @since 0.12
+ */
+public final class PrometheusStatsCollector extends Collector implements Collector.Describable {
+
+ private static final Logger logger = Logger.getLogger(PrometheusStatsCollector.class.getName());
+
+ private final ViewManager viewManager;
+
+ /**
+ * Creates a {@link PrometheusStatsCollector} and registers it to Prometheus {@link
+ * CollectorRegistry#defaultRegistry}.
+ *
+ * @throws IllegalArgumentException if a {@code PrometheusStatsCollector} has already been created
+ * and registered.
+ * @since 0.12
+ */
+ public static void createAndRegister() {
+ new PrometheusStatsCollector(Stats.getViewManager()).register();
+ }
+
+ @Override
+ public List<MetricFamilySamples> collect() {
+ List<MetricFamilySamples> samples = Lists.newArrayList();
+ try {
+ for (View view : viewManager.getAllExportedViews()) {
+ ViewData viewData = viewManager.getView(view.getName());
+ if (viewData == null) {
+ continue;
+ } else {
+ samples.add(PrometheusExportUtils.createMetricFamilySamples(viewData));
+ }
+ }
+ } catch (Throwable e) {
+ logger.log(Level.WARNING, "Exception thrown when collecting metric samples.", e);
+ }
+ return samples;
+ }
+
+ @Override
+ public List<MetricFamilySamples> describe() {
+ List<MetricFamilySamples> samples = Lists.newArrayList();
+ try {
+ for (View view : viewManager.getAllExportedViews()) {
+ samples.add(PrometheusExportUtils.createDescribableMetricFamilySamples(view));
+ }
+ } catch (Throwable e) {
+ logger.log(Level.WARNING, "Exception thrown when describing metrics.", e);
+ }
+ return samples;
+ }
+
+ @VisibleForTesting
+ PrometheusStatsCollector(ViewManager viewManager) {
+ this.viewManager = viewManager;
+ }
+}
diff --git a/exporters/stats/prometheus/src/test/java/io/opencensus/exporter/stats/prometheus/PrometheusExportUtilsTest.java b/exporters/stats/prometheus/src/test/java/io/opencensus/exporter/stats/prometheus/PrometheusExportUtilsTest.java
new file mode 100644
index 00000000..9629328d
--- /dev/null
+++ b/exporters/stats/prometheus/src/test/java/io/opencensus/exporter/stats/prometheus/PrometheusExportUtilsTest.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2018, OpenCensus 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.opencensus.exporter.stats.prometheus;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.opencensus.exporter.stats.prometheus.PrometheusExportUtils.OPENCENSUS_HELP_MSG;
+import static io.opencensus.exporter.stats.prometheus.PrometheusExportUtils.OPENCENSUS_NAMESPACE;
+import static io.opencensus.exporter.stats.prometheus.PrometheusExportUtils.SAMPLE_SUFFIX_BUCKET;
+import static io.opencensus.exporter.stats.prometheus.PrometheusExportUtils.SAMPLE_SUFFIX_COUNT;
+import static io.opencensus.exporter.stats.prometheus.PrometheusExportUtils.SAMPLE_SUFFIX_SUM;
+
+import com.google.common.collect.ImmutableMap;
+import io.opencensus.common.Duration;
+import io.opencensus.common.Timestamp;
+import io.opencensus.stats.Aggregation.Count;
+import io.opencensus.stats.Aggregation.Distribution;
+import io.opencensus.stats.Aggregation.Mean;
+import io.opencensus.stats.Aggregation.Sum;
+import io.opencensus.stats.AggregationData.CountData;
+import io.opencensus.stats.AggregationData.DistributionData;
+import io.opencensus.stats.AggregationData.MeanData;
+import io.opencensus.stats.AggregationData.SumDataDouble;
+import io.opencensus.stats.AggregationData.SumDataLong;
+import io.opencensus.stats.BucketBoundaries;
+import io.opencensus.stats.Measure.MeasureDouble;
+import io.opencensus.stats.View;
+import io.opencensus.stats.View.AggregationWindow.Cumulative;
+import io.opencensus.stats.View.AggregationWindow.Interval;
+import io.opencensus.stats.ViewData;
+import io.opencensus.stats.ViewData.AggregationWindowData.CumulativeData;
+import io.opencensus.stats.ViewData.AggregationWindowData.IntervalData;
+import io.opencensus.tags.TagKey;
+import io.opencensus.tags.TagValue;
+import io.prometheus.client.Collector.MetricFamilySamples;
+import io.prometheus.client.Collector.MetricFamilySamples.Sample;
+import io.prometheus.client.Collector.Type;
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link PrometheusExportUtils}. */
+@RunWith(JUnit4.class)
+public class PrometheusExportUtilsTest {
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ private static final Duration ONE_SECOND = Duration.create(1, 0);
+ private static final Cumulative CUMULATIVE = Cumulative.create();
+ private static final Interval INTERVAL = Interval.create(ONE_SECOND);
+ private static final Sum SUM = Sum.create();
+ private static final Count COUNT = Count.create();
+ private static final Mean MEAN = Mean.create();
+ private static final BucketBoundaries BUCKET_BOUNDARIES =
+ BucketBoundaries.create(Arrays.asList(-5.0, 0.0, 5.0));
+ private static final Distribution DISTRIBUTION = Distribution.create(BUCKET_BOUNDARIES);
+ private static final View.Name VIEW_NAME_1 = View.Name.create("view1");
+ private static final View.Name VIEW_NAME_2 = View.Name.create("view2");
+ private static final View.Name VIEW_NAME_3 = View.Name.create("view-3");
+ private static final View.Name VIEW_NAME_4 = View.Name.create("-view4");
+ private static final String DESCRIPTION = "View description";
+ private static final MeasureDouble MEASURE_DOUBLE =
+ MeasureDouble.create("measure", "description", "1");
+ private static final TagKey K1 = TagKey.create("k1");
+ private static final TagKey K2 = TagKey.create("k2");
+ private static final TagKey K3 = TagKey.create("k-3");
+ private static final TagValue V1 = TagValue.create("v1");
+ private static final TagValue V2 = TagValue.create("v2");
+ private static final TagValue V3 = TagValue.create("v-3");
+ private static final SumDataDouble SUM_DATA_DOUBLE = SumDataDouble.create(-5.5);
+ private static final SumDataLong SUM_DATA_LONG = SumDataLong.create(123456789);
+ private static final CountData COUNT_DATA = CountData.create(12345);
+ private static final MeanData MEAN_DATA = MeanData.create(3.4, 22);
+ private static final DistributionData DISTRIBUTION_DATA =
+ DistributionData.create(4.4, 5, -3.2, 15.7, 135.22, Arrays.asList(0L, 2L, 2L, 1L));
+ private static final View VIEW1 =
+ View.create(
+ VIEW_NAME_1, DESCRIPTION, MEASURE_DOUBLE, COUNT, Arrays.asList(K1, K2), CUMULATIVE);
+ private static final View VIEW2 =
+ View.create(VIEW_NAME_2, DESCRIPTION, MEASURE_DOUBLE, MEAN, Arrays.asList(K3), CUMULATIVE);
+ private static final View VIEW3 =
+ View.create(
+ VIEW_NAME_3, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(K1), CUMULATIVE);
+ private static final View VIEW4 =
+ View.create(VIEW_NAME_4, DESCRIPTION, MEASURE_DOUBLE, COUNT, Arrays.asList(K1), INTERVAL);
+ private static final CumulativeData CUMULATIVE_DATA =
+ CumulativeData.create(Timestamp.fromMillis(1000), Timestamp.fromMillis(2000));
+ private static final IntervalData INTERVAL_DATA = IntervalData.create(Timestamp.fromMillis(1000));
+ private static final String SAMPLE_NAME = OPENCENSUS_NAMESPACE + "_view";
+
+ @Test
+ public void testConstants() {
+ assertThat(OPENCENSUS_NAMESPACE).isEqualTo("opencensus");
+ assertThat(OPENCENSUS_HELP_MSG).isEqualTo("Opencensus Prometheus metrics: ");
+ assertThat(SAMPLE_SUFFIX_BUCKET).isEqualTo("_bucket");
+ assertThat(SAMPLE_SUFFIX_COUNT).isEqualTo("_count");
+ assertThat(SAMPLE_SUFFIX_SUM).isEqualTo("_sum");
+ }
+
+ @Test
+ public void getType() {
+ assertThat(PrometheusExportUtils.getType(COUNT, INTERVAL)).isEqualTo(Type.UNTYPED);
+ assertThat(PrometheusExportUtils.getType(COUNT, CUMULATIVE)).isEqualTo(Type.COUNTER);
+ assertThat(PrometheusExportUtils.getType(DISTRIBUTION, CUMULATIVE)).isEqualTo(Type.HISTOGRAM);
+ assertThat(PrometheusExportUtils.getType(SUM, CUMULATIVE)).isEqualTo(Type.UNTYPED);
+ assertThat(PrometheusExportUtils.getType(MEAN, CUMULATIVE)).isEqualTo(Type.SUMMARY);
+ }
+
+ @Test
+ public void createDescribableMetricFamilySamples() {
+ assertThat(PrometheusExportUtils.createDescribableMetricFamilySamples(VIEW1))
+ .isEqualTo(
+ new MetricFamilySamples(
+ OPENCENSUS_NAMESPACE + "_view1",
+ Type.COUNTER,
+ OPENCENSUS_HELP_MSG + DESCRIPTION,
+ Collections.<Sample>emptyList()));
+ assertThat(PrometheusExportUtils.createDescribableMetricFamilySamples(VIEW2))
+ .isEqualTo(
+ new MetricFamilySamples(
+ OPENCENSUS_NAMESPACE + "_view2",
+ Type.SUMMARY,
+ OPENCENSUS_HELP_MSG + DESCRIPTION,
+ Collections.<Sample>emptyList()));
+ assertThat(PrometheusExportUtils.createDescribableMetricFamilySamples(VIEW3))
+ .isEqualTo(
+ new MetricFamilySamples(
+ OPENCENSUS_NAMESPACE + "_view_3",
+ Type.HISTOGRAM,
+ OPENCENSUS_HELP_MSG + DESCRIPTION,
+ Collections.<Sample>emptyList()));
+ assertThat(PrometheusExportUtils.createDescribableMetricFamilySamples(VIEW4))
+ .isEqualTo(
+ new MetricFamilySamples(
+ OPENCENSUS_NAMESPACE + "__view4",
+ Type.UNTYPED,
+ OPENCENSUS_HELP_MSG + DESCRIPTION,
+ Collections.<Sample>emptyList()));
+ }
+
+ @Test
+ public void getSamples() {
+ assertThat(
+ PrometheusExportUtils.getSamples(
+ SAMPLE_NAME, Arrays.asList(K1, K2), Arrays.asList(V1, V2), SUM_DATA_DOUBLE))
+ .containsExactly(
+ new Sample(SAMPLE_NAME, Arrays.asList("k1", "k2"), Arrays.asList("v1", "v2"), -5.5));
+ assertThat(
+ PrometheusExportUtils.getSamples(
+ SAMPLE_NAME, Arrays.asList(K3), Arrays.asList(V3), SUM_DATA_LONG))
+ .containsExactly(
+ new Sample(SAMPLE_NAME, Arrays.asList("k_3"), Arrays.asList("v-3"), 123456789));
+ assertThat(
+ PrometheusExportUtils.getSamples(
+ SAMPLE_NAME, Arrays.asList(K1, K3), Arrays.asList(V1, null), COUNT_DATA))
+ .containsExactly(
+ new Sample(SAMPLE_NAME, Arrays.asList("k1", "k_3"), Arrays.asList("v1", ""), 12345));
+ assertThat(
+ PrometheusExportUtils.getSamples(
+ SAMPLE_NAME, Arrays.asList(K3), Arrays.asList(V3), MEAN_DATA))
+ .containsExactly(
+ new Sample(SAMPLE_NAME + "_count", Arrays.asList("k_3"), Arrays.asList("v-3"), 22),
+ new Sample(SAMPLE_NAME + "_sum", Arrays.asList("k_3"), Arrays.asList("v-3"), 74.8))
+ .inOrder();
+ assertThat(
+ PrometheusExportUtils.getSamples(
+ SAMPLE_NAME, Arrays.asList(K1), Arrays.asList(V1), DISTRIBUTION_DATA))
+ .containsExactly(
+ new Sample(SAMPLE_NAME + "_bucket", Arrays.asList("k1"), Arrays.asList("v1"), 0),
+ new Sample(SAMPLE_NAME + "_bucket", Arrays.asList("k1"), Arrays.asList("v1"), 2),
+ new Sample(SAMPLE_NAME + "_bucket", Arrays.asList("k1"), Arrays.asList("v1"), 2),
+ new Sample(SAMPLE_NAME + "_bucket", Arrays.asList("k1"), Arrays.asList("v1"), 1),
+ new Sample(SAMPLE_NAME + "_count", Arrays.asList("k1"), Arrays.asList("v1"), 5),
+ new Sample(SAMPLE_NAME + "_sum", Arrays.asList("k1"), Arrays.asList("v1"), 22.0))
+ .inOrder();
+ }
+
+ @Test
+ public void getSamples_KeysAndValuesHaveDifferentSizes() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Tag keys and tag values have different sizes.");
+ PrometheusExportUtils.getSamples(
+ SAMPLE_NAME, Arrays.asList(K1, K2, K3), Arrays.asList(V1, V2), DISTRIBUTION_DATA);
+ }
+
+ @Test
+ public void createMetricFamilySamples() {
+ assertThat(
+ PrometheusExportUtils.createMetricFamilySamples(
+ ViewData.create(
+ VIEW1, ImmutableMap.of(Arrays.asList(V1, V2), COUNT_DATA), CUMULATIVE_DATA)))
+ .isEqualTo(
+ new MetricFamilySamples(
+ OPENCENSUS_NAMESPACE + "_view1",
+ Type.COUNTER,
+ OPENCENSUS_HELP_MSG + DESCRIPTION,
+ Arrays.asList(
+ new Sample(
+ OPENCENSUS_NAMESPACE + "_view1",
+ Arrays.asList("k1", "k2"),
+ Arrays.asList("v1", "v2"),
+ 12345))));
+ assertThat(
+ PrometheusExportUtils.createMetricFamilySamples(
+ ViewData.create(
+ VIEW2, ImmutableMap.of(Arrays.asList(V1), MEAN_DATA), CUMULATIVE_DATA)))
+ .isEqualTo(
+ new MetricFamilySamples(
+ OPENCENSUS_NAMESPACE + "_view2",
+ Type.SUMMARY,
+ OPENCENSUS_HELP_MSG + DESCRIPTION,
+ Arrays.asList(
+ new Sample(
+ OPENCENSUS_NAMESPACE + "_view2_count",
+ Arrays.asList("k_3"),
+ Arrays.asList("v1"),
+ 22),
+ new Sample(
+ OPENCENSUS_NAMESPACE + "_view2_sum",
+ Arrays.asList("k_3"),
+ Arrays.asList("v1"),
+ 74.8))));
+ assertThat(
+ PrometheusExportUtils.createMetricFamilySamples(
+ ViewData.create(
+ VIEW3, ImmutableMap.of(Arrays.asList(V3), DISTRIBUTION_DATA), CUMULATIVE_DATA)))
+ .isEqualTo(
+ new MetricFamilySamples(
+ OPENCENSUS_NAMESPACE + "_view_3",
+ Type.HISTOGRAM,
+ OPENCENSUS_HELP_MSG + DESCRIPTION,
+ Arrays.asList(
+ new Sample(
+ OPENCENSUS_NAMESPACE + "_view_3_bucket",
+ Arrays.asList("k1"),
+ Arrays.asList("v-3"),
+ 0),
+ new Sample(
+ OPENCENSUS_NAMESPACE + "_view_3_bucket",
+ Arrays.asList("k1"),
+ Arrays.asList("v-3"),
+ 2),
+ new Sample(
+ OPENCENSUS_NAMESPACE + "_view_3_bucket",
+ Arrays.asList("k1"),
+ Arrays.asList("v-3"),
+ 2),
+ new Sample(
+ OPENCENSUS_NAMESPACE + "_view_3_bucket",
+ Arrays.asList("k1"),
+ Arrays.asList("v-3"),
+ 1),
+ new Sample(
+ OPENCENSUS_NAMESPACE + "_view_3_count",
+ Arrays.asList("k1"),
+ Arrays.asList("v-3"),
+ 5),
+ new Sample(
+ OPENCENSUS_NAMESPACE + "_view_3_sum",
+ Arrays.asList("k1"),
+ Arrays.asList("v-3"),
+ 22.0))));
+ assertThat(
+ PrometheusExportUtils.createMetricFamilySamples(
+ ViewData.create(
+ VIEW4, ImmutableMap.of(Arrays.asList(V1), COUNT_DATA), INTERVAL_DATA)))
+ .isEqualTo(
+ new MetricFamilySamples(
+ OPENCENSUS_NAMESPACE + "__view4",
+ Type.UNTYPED,
+ OPENCENSUS_HELP_MSG + DESCRIPTION,
+ Arrays.asList(
+ new Sample(
+ OPENCENSUS_NAMESPACE + "__view4",
+ Arrays.asList("k1"),
+ Arrays.asList("v1"),
+ 12345))));
+ }
+}
diff --git a/exporters/stats/prometheus/src/test/java/io/opencensus/exporter/stats/prometheus/PrometheusStatsCollectorTest.java b/exporters/stats/prometheus/src/test/java/io/opencensus/exporter/stats/prometheus/PrometheusStatsCollectorTest.java
new file mode 100644
index 00000000..b10164e7
--- /dev/null
+++ b/exporters/stats/prometheus/src/test/java/io/opencensus/exporter/stats/prometheus/PrometheusStatsCollectorTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2018, OpenCensus 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.opencensus.exporter.stats.prometheus;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import io.opencensus.common.Timestamp;
+import io.opencensus.stats.Aggregation.Distribution;
+import io.opencensus.stats.AggregationData.DistributionData;
+import io.opencensus.stats.BucketBoundaries;
+import io.opencensus.stats.Measure.MeasureDouble;
+import io.opencensus.stats.Stats;
+import io.opencensus.stats.View;
+import io.opencensus.stats.View.AggregationWindow.Cumulative;
+import io.opencensus.stats.ViewData;
+import io.opencensus.stats.ViewData.AggregationWindowData.CumulativeData;
+import io.opencensus.stats.ViewManager;
+import io.opencensus.tags.TagKey;
+import io.opencensus.tags.TagValue;
+import io.prometheus.client.Collector.MetricFamilySamples;
+import io.prometheus.client.Collector.MetricFamilySamples.Sample;
+import io.prometheus.client.Collector.Type;
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link PrometheusStatsCollector}. */
+@RunWith(JUnit4.class)
+public class PrometheusStatsCollectorTest {
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ private static final Cumulative CUMULATIVE = Cumulative.create();
+ private static final BucketBoundaries BUCKET_BOUNDARIES =
+ BucketBoundaries.create(Arrays.asList(-5.0, 0.0, 5.0));
+ private static final Distribution DISTRIBUTION = Distribution.create(BUCKET_BOUNDARIES);
+ private static final View.Name VIEW_NAME = View.Name.create("view1");
+ private static final String DESCRIPTION = "View description";
+ private static final MeasureDouble MEASURE_DOUBLE =
+ MeasureDouble.create("measure", "description", "1");
+ private static final TagKey K1 = TagKey.create("k1");
+ private static final TagKey K2 = TagKey.create("k2");
+ private static final TagValue V1 = TagValue.create("v1");
+ private static final TagValue V2 = TagValue.create("v2");
+ private static final DistributionData DISTRIBUTION_DATA =
+ DistributionData.create(4.4, 5, -3.2, 15.7, 135.22, Arrays.asList(0L, 2L, 2L, 1L));
+ private static final View VIEW =
+ View.create(
+ VIEW_NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(K1, K2), CUMULATIVE);
+ private static final CumulativeData CUMULATIVE_DATA =
+ CumulativeData.create(Timestamp.fromMillis(1000), Timestamp.fromMillis(2000));
+ private static final ViewData VIEW_DATA =
+ ViewData.create(
+ VIEW, ImmutableMap.of(Arrays.asList(V1, V2), DISTRIBUTION_DATA), CUMULATIVE_DATA);
+
+ @Mock private ViewManager mockViewManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(ImmutableSet.of(VIEW)).when(mockViewManager).getAllExportedViews();
+ doReturn(VIEW_DATA).when(mockViewManager).getView(VIEW_NAME);
+ }
+
+ @Test
+ public void testCollect() {
+ PrometheusStatsCollector collector = new PrometheusStatsCollector(mockViewManager);
+ String name = "opencensus_view1";
+ assertThat(collector.collect())
+ .containsExactly(
+ new MetricFamilySamples(
+ "opencensus_view1",
+ Type.HISTOGRAM,
+ "Opencensus Prometheus metrics: View description",
+ Arrays.asList(
+ new Sample(
+ name + "_bucket", Arrays.asList("k1", "k2"), Arrays.asList("v1", "v2"), 0),
+ new Sample(
+ name + "_bucket", Arrays.asList("k1", "k2"), Arrays.asList("v1", "v2"), 2),
+ new Sample(
+ name + "_bucket", Arrays.asList("k1", "k2"), Arrays.asList("v1", "v2"), 2),
+ new Sample(
+ name + "_bucket", Arrays.asList("k1", "k2"), Arrays.asList("v1", "v2"), 1),
+ new Sample(
+ name + "_count", Arrays.asList("k1", "k2"), Arrays.asList("v1", "v2"), 5),
+ new Sample(
+ name + "_sum",
+ Arrays.asList("k1", "k2"),
+ Arrays.asList("v1", "v2"),
+ 22.0))));
+ }
+
+ @Test
+ public void testDescribe() {
+ PrometheusStatsCollector collector = new PrometheusStatsCollector(mockViewManager);
+ assertThat(collector.describe())
+ .containsExactly(
+ new MetricFamilySamples(
+ "opencensus_view1",
+ Type.HISTOGRAM,
+ "Opencensus Prometheus metrics: View description",
+ Collections.<Sample>emptyList()));
+ }
+
+ @Test
+ public void testCollect_WithNoopViewManager() {
+ PrometheusStatsCollector collector = new PrometheusStatsCollector(Stats.getViewManager());
+ assertThat(collector.collect()).isEmpty();
+ }
+
+ @Test
+ public void testDescribe_WithNoopViewManager() {
+ PrometheusStatsCollector collector = new PrometheusStatsCollector(Stats.getViewManager());
+ assertThat(collector.describe()).isEmpty();
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 6921de56..093e0423 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -11,6 +11,7 @@ include ":opencensus-exporter-trace-stackdriver"
include ":opencensus-exporter-trace-zipkin"
include ":opencensus-exporter-stats-signalfx"
include ":opencensus-exporter-stats-stackdriver"
+include ":opencensus-exporter-stats-prometheus"
include ":opencensus-contrib-agent"
include ":opencensus-contrib-grpc-metrics"
include ":opencensus-contrib-grpc-util"
@@ -34,6 +35,8 @@ project(':opencensus-exporter-trace-stackdriver').projectDir =
project(':opencensus-exporter-trace-zipkin').projectDir = "$rootDir/exporters/trace/zipkin" as File
project(':opencensus-exporter-stats-signalfx').projectDir = "$rootDir/exporters/stats/signalfx" as File
project(':opencensus-exporter-stats-stackdriver').projectDir = "$rootDir/exporters/stats/stackdriver" as File
+project(':opencensus-exporter-stats-prometheus').projectDir = "$rootDir/exporters/stats/prometheus" as File
+
// Java8 projects only
if (JavaVersion.current().isJava8Compatible()) {