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

Commit 88fb9f4

Browse files
committed
Set bucket bounds as le labels for Prometheus exporter. (#1167)
Closes #1164. Previously we don't set the bucket bounds for Prometheus Samples, since bucket bounds is a built-in feature for Prometheus Histogram. However, as #1164 suggested, we can manually set the bucket bounds with the same label le that the built-in Histogram uses, to get the same output as the built-in Prometheus Histogram Samples. (cherry picked from commit 3838c4c)
1 parent 846a5b0 commit 88fb9f4

File tree

6 files changed

+215
-53
lines changed

6 files changed

+215
-53
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## Unreleased
22
- Fix a typo on displaying Aggregation Type for a View on StatsZ page.
3+
- Set bucket bounds as "le" labels for Prometheus Stats exporter.
34

45
## 0.13.0 - 2018-04-27
56
- Support building with Java 9.

exporters/stats/prometheus/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,4 @@ and [Bridges](https://github.com/prometheus/client_java#bridges).
7878

7979
Java 7 or above is required for using this exporter.
8080

81-
## FAQ
81+
## FAQ

exporters/stats/prometheus/src/main/java/io/opencensus/exporter/stats/prometheus/PrometheusExportUtils.java

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package io.opencensus.exporter.stats.prometheus;
1818

19+
import static io.prometheus.client.Collector.doubleToGoString;
20+
1921
import com.google.common.annotations.VisibleForTesting;
2022
import com.google.common.base.Preconditions;
2123
import com.google.common.collect.Lists;
@@ -86,17 +88,20 @@ final class PrometheusExportUtils {
8688
@VisibleForTesting static final String SAMPLE_SUFFIX_BUCKET = "_bucket";
8789
@VisibleForTesting static final String SAMPLE_SUFFIX_COUNT = "_count";
8890
@VisibleForTesting static final String SAMPLE_SUFFIX_SUM = "_sum";
91+
@VisibleForTesting static final String LABEL_NAME_BUCKET_BOUND = "le";
8992

9093
// Converts a ViewData to a Prometheus MetricFamilySamples.
9194
static MetricFamilySamples createMetricFamilySamples(ViewData viewData) {
9295
View view = viewData.getView();
9396
String name =
9497
Collector.sanitizeMetricName(OPENCENSUS_NAMESPACE + '_' + view.getName().asString());
9598
Type type = getType(view.getAggregation(), view.getWindow());
99+
List<String> labelNames = convertToLabelNames(view.getColumns());
96100
List<Sample> samples = Lists.newArrayList();
97101
for (Entry<List</*@Nullable*/ TagValue>, AggregationData> entry :
98102
viewData.getAggregationMap().entrySet()) {
99-
samples.addAll(getSamples(name, view.getColumns(), entry.getKey(), entry.getValue()));
103+
samples.addAll(
104+
getSamples(name, labelNames, entry.getKey(), entry.getValue(), view.getAggregation()));
100105
}
101106
return new MetricFamilySamples(
102107
name, type, OPENCENSUS_HELP_MSG + view.getDescription(), samples);
@@ -108,6 +113,13 @@ static MetricFamilySamples createDescribableMetricFamilySamples(View view) {
108113
String name =
109114
Collector.sanitizeMetricName(OPENCENSUS_NAMESPACE + '_' + view.getName().asString());
110115
Type type = getType(view.getAggregation(), view.getWindow());
116+
List<String> labelNames = convertToLabelNames(view.getColumns());
117+
if (containsDisallowedLeLabelForHistogram(labelNames, type)) {
118+
throw new IllegalStateException(
119+
"Prometheus Histogram cannot have a label named 'le', "
120+
+ "because it is a reserved label for bucket boundaries. "
121+
+ "Please remove this tag key from your view.");
122+
}
111123
return new MetricFamilySamples(
112124
name, type, OPENCENSUS_HELP_MSG + view.getDescription(), Collections.<Sample>emptyList());
113125
}
@@ -133,20 +145,19 @@ public Type apply(Aggregation arg) {
133145
});
134146
}
135147

148+
// Converts a row in ViewData (a.k.a Entry<List<TagValue>, AggregationData>) to a list of
149+
// Prometheus Samples.
136150
@VisibleForTesting
137151
static List<Sample> getSamples(
138152
final String name,
139-
List<TagKey> tagKeys,
153+
final List<String> labelNames,
140154
List</*@Nullable*/ TagValue> tagValues,
141-
AggregationData aggregationData) {
155+
AggregationData aggregationData,
156+
final Aggregation aggregation) {
142157
Preconditions.checkArgument(
143-
tagKeys.size() == tagValues.size(), "Tag keys and tag values have different sizes.");
158+
labelNames.size() == tagValues.size(), "Label names and tag values have different sizes.");
144159
final List<Sample> samples = Lists.newArrayList();
145-
final List<String> labelNames = new ArrayList<String>(tagKeys.size());
146160
final List<String> labelValues = new ArrayList<String>(tagValues.size());
147-
for (TagKey tagKey : tagKeys) {
148-
labelNames.add(Collector.sanitizeMetricName(tagKey.getName()));
149-
}
150161
for (TagValue tagValue : tagValues) {
151162
String labelValue = tagValue == null ? "" : tagValue.asString();
152163
labelValues.add(labelValue);
@@ -177,11 +188,29 @@ public Void apply(CountData arg) {
177188
new Function<DistributionData, Void>() {
178189
@Override
179190
public Void apply(DistributionData arg) {
180-
for (long bucketCount : arg.getBucketCounts()) {
191+
// For histogram buckets, manually add the bucket boundaries as "le" labels. See
192+
// https://github.com/prometheus/client_java/commit/ed184d8e50c82e98bb2706723fff764424840c3a#diff-c505abbde72dd6bf36e89917b3469404R241
193+
@SuppressWarnings("unchecked")
194+
Distribution distribution = (Distribution) aggregation;
195+
List<Double> boundaries = distribution.getBucketBoundaries().getBoundaries();
196+
List<String> labelNamesWithLe = new ArrayList<String>(labelNames);
197+
labelNamesWithLe.add(LABEL_NAME_BUCKET_BOUND);
198+
for (int i = 0; i < arg.getBucketCounts().size(); i++) {
199+
List<String> labelValuesWithLe = new ArrayList<String>(labelValues);
200+
// The label value of "le" is the upper inclusive bound.
201+
// For the last bucket, it should be "+Inf".
202+
String bucketBoundary =
203+
doubleToGoString(
204+
i < boundaries.size() ? boundaries.get(i) : Double.POSITIVE_INFINITY);
205+
labelValuesWithLe.add(bucketBoundary);
181206
samples.add(
182207
new MetricFamilySamples.Sample(
183-
name + SAMPLE_SUFFIX_BUCKET, labelNames, labelValues, bucketCount));
208+
name + SAMPLE_SUFFIX_BUCKET,
209+
labelNamesWithLe,
210+
labelValuesWithLe,
211+
arg.getBucketCounts().get(i)));
184212
}
213+
185214
samples.add(
186215
new MetricFamilySamples.Sample(
187216
name + SAMPLE_SUFFIX_COUNT, labelNames, labelValues, arg.getCount()));
@@ -234,5 +263,30 @@ public Void apply(AggregationData arg) {
234263
return samples;
235264
}
236265

266+
// Converts the list of tag keys to a list of string label names. Also sanitizes the tag keys.
267+
@VisibleForTesting
268+
static List<String> convertToLabelNames(List<TagKey> tagKeys) {
269+
final List<String> labelNames = new ArrayList<String>(tagKeys.size());
270+
for (TagKey tagKey : tagKeys) {
271+
labelNames.add(Collector.sanitizeMetricName(tagKey.getName()));
272+
}
273+
return labelNames;
274+
}
275+
276+
// Returns true if there is an "le" label name in histogram label names, returns false otherwise.
277+
// Similar check to
278+
// https://github.com/prometheus/client_java/commit/ed184d8e50c82e98bb2706723fff764424840c3a#diff-c505abbde72dd6bf36e89917b3469404R78
279+
static boolean containsDisallowedLeLabelForHistogram(List<String> labelNames, Type type) {
280+
if (!Type.HISTOGRAM.equals(type)) {
281+
return false;
282+
}
283+
for (String label : labelNames) {
284+
if (LABEL_NAME_BUCKET_BOUND.equals(label)) {
285+
return true;
286+
}
287+
}
288+
return false;
289+
}
290+
237291
private PrometheusExportUtils() {}
238292
}

exporters/stats/prometheus/src/main/java/io/opencensus/exporter/stats/prometheus/PrometheusStatsCollector.java

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package io.opencensus.exporter.stats.prometheus;
1818

19+
import static io.opencensus.exporter.stats.prometheus.PrometheusExportUtils.containsDisallowedLeLabelForHistogram;
20+
import static io.opencensus.exporter.stats.prometheus.PrometheusExportUtils.convertToLabelNames;
21+
import static io.opencensus.exporter.stats.prometheus.PrometheusExportUtils.getType;
22+
1923
import com.google.common.annotations.VisibleForTesting;
2024
import com.google.common.collect.ImmutableList;
2125
import com.google.common.collect.Lists;
@@ -39,6 +43,7 @@
3943
*
4044
* @since 0.12
4145
*/
46+
@SuppressWarnings("deprecation")
4247
public final class PrometheusStatsCollector extends Collector implements Collector.Describable {
4348

4449
private static final Logger logger = Logger.getLogger(PrometheusStatsCollector.class.getName());
@@ -90,20 +95,27 @@ public List<MetricFamilySamples> collect() {
9095
span.addAnnotation("Collect Prometheus Metric Samples.");
9196
try (Scope scope = tracer.withSpan(span)) {
9297
for (View view : viewManager.getAllExportedViews()) {
93-
ViewData viewData = viewManager.getView(view.getName());
94-
if (viewData == null) {
95-
continue;
96-
} else {
97-
samples.add(PrometheusExportUtils.createMetricFamilySamples(viewData));
98+
if (containsDisallowedLeLabelForHistogram(
99+
convertToLabelNames(view.getColumns()),
100+
getType(view.getAggregation(), view.getWindow()))) {
101+
continue; // silently skip Distribution views with "le" tag key
102+
}
103+
try {
104+
ViewData viewData = viewManager.getView(view.getName());
105+
if (viewData == null) {
106+
continue;
107+
} else {
108+
samples.add(PrometheusExportUtils.createMetricFamilySamples(viewData));
109+
}
110+
} catch (Throwable e) {
111+
logger.log(Level.WARNING, "Exception thrown when collecting metric samples.", e);
112+
span.setStatus(
113+
Status.UNKNOWN.withDescription(
114+
"Exception thrown when collecting Prometheus Metric Samples: "
115+
+ exceptionMessage(e)));
98116
}
99117
}
100118
span.addAnnotation("Finish collecting Prometheus Metric Samples.");
101-
} catch (Throwable e) {
102-
logger.log(Level.WARNING, "Exception thrown when collecting metric samples.", e);
103-
span.setStatus(
104-
Status.UNKNOWN.withDescription(
105-
"Exception thrown when collecting Prometheus Metric Samples: "
106-
+ exceptionMessage(e)));
107119
} finally {
108120
span.end();
109121
}
@@ -118,14 +130,16 @@ public List<MetricFamilySamples> describe() {
118130
span.addAnnotation("Describe Prometheus Metrics.");
119131
try (Scope scope = tracer.withSpan(span)) {
120132
for (View view : viewManager.getAllExportedViews()) {
121-
samples.add(PrometheusExportUtils.createDescribableMetricFamilySamples(view));
133+
try {
134+
samples.add(PrometheusExportUtils.createDescribableMetricFamilySamples(view));
135+
} catch (Throwable e) {
136+
logger.log(Level.WARNING, "Exception thrown when describing metrics.", e);
137+
span.setStatus(
138+
Status.UNKNOWN.withDescription(
139+
"Exception thrown when describing Prometheus Metrics: " + exceptionMessage(e)));
140+
}
122141
}
123142
span.addAnnotation("Finish describing Prometheus Metrics.");
124-
} catch (Throwable e) {
125-
logger.log(Level.WARNING, "Exception thrown when describing metrics.", e);
126-
span.setStatus(
127-
Status.UNKNOWN.withDescription(
128-
"Exception thrown when describing Prometheus Metrics: " + exceptionMessage(e)));
129143
} finally {
130144
span.end();
131145
}

0 commit comments

Comments
 (0)