1616
1717package io .opencensus .exporter .stats .prometheus ;
1818
19+ import static io .prometheus .client .Collector .doubleToGoString ;
20+
1921import com .google .common .annotations .VisibleForTesting ;
2022import com .google .common .base .Preconditions ;
2123import 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}
0 commit comments