46
46
import java .io .UncheckedIOException ;
47
47
import java .io .Writer ;
48
48
import java .nio .charset .StandardCharsets ;
49
+ import java .util .ArrayList ;
49
50
import java .util .Collection ;
50
51
import java .util .Collections ;
52
+ import java .util .HashSet ;
51
53
import java .util .LinkedHashMap ;
54
+ import java .util .LinkedHashSet ;
52
55
import java .util .List ;
53
56
import java .util .Map ;
54
57
import java .util .Optional ;
58
+ import java .util .Set ;
55
59
import java .util .concurrent .TimeUnit ;
56
60
import java .util .function .BiConsumer ;
57
61
import java .util .function .Predicate ;
58
- import java .util .stream .Collectors ;
59
62
import javax .annotation .Nullable ;
60
63
61
64
/** Serializes metrics into Prometheus exposition formats. */
62
65
// Adapted from
63
66
// https://github.com/prometheus/client_java/blob/master/simpleclient_common/src/main/java/io/prometheus/client/exporter/common/TextFormat.java
64
67
abstract class Serializer {
65
-
66
68
static Serializer create (@ Nullable String acceptHeader , Predicate <String > filter ) {
67
69
if (acceptHeader == null ) {
68
70
return new Prometheus004Serializer (filter );
@@ -100,61 +102,64 @@ abstract void writeExemplar(
100
102
101
103
abstract void writeEof (Writer writer ) throws IOException ;
102
104
103
- final void write (Collection <MetricData > metrics , OutputStream output ) throws IOException {
104
- Map <InstrumentationScopeInfo , List <MetricData >> metricsByScope =
105
- metrics .stream ()
106
- // Not supported in specification yet.
107
- .filter (metric -> metric .getType () != MetricDataType .EXPONENTIAL_HISTOGRAM )
108
- // PrometheusHttpServer#getAggregationTemporality specifies cumulative temporality for
109
- // all instruments, but non-SDK MetricProducers may not conform. We drop delta
110
- // temporality metrics to avoid the complexity of stateful transformation to cumulative.
111
- .filter (metric -> !isDeltaTemporality (metric ))
112
- .filter (metric -> metricNameFilter .test (metricName (metric )))
113
- .collect (
114
- Collectors .groupingBy (
115
- MetricData ::getInstrumentationScopeInfo ,
116
- LinkedHashMap ::new ,
117
- Collectors .toList ()));
105
+ final Set <String > write (Collection <MetricData > metrics , OutputStream output ) throws IOException {
106
+ Set <String > conflictMetricNames = new HashSet <>();
107
+ Map <String , List <MetricData >> metricsByName = new LinkedHashMap <>();
108
+ Set <InstrumentationScopeInfo > scopes = new LinkedHashSet <>();
109
+ // Iterate through metrics, filtering and grouping by headerName
110
+ for (MetricData metric : metrics ) {
111
+ // Not supported in specification yet.
112
+ if (metric .getType () == MetricDataType .EXPONENTIAL_HISTOGRAM ) {
113
+ continue ;
114
+ }
115
+ // PrometheusHttpServer#getAggregationTemporality specifies cumulative temporality for
116
+ // all instruments, but non-SDK MetricProducers may not conform. We drop delta
117
+ // temporality metrics to avoid the complexity of stateful transformation to cumulative.
118
+ if (isDeltaTemporality (metric )) {
119
+ continue ;
120
+ }
121
+ PrometheusType prometheusType = PrometheusType .forMetric (metric );
122
+ String metricName = metricName (metric .getName (), prometheusType );
123
+ // Skip metrics which do not pass metricNameFilter
124
+ if (!metricNameFilter .test (metricName )) {
125
+ continue ;
126
+ }
127
+ List <MetricData > metricsWithHeaderName =
128
+ metricsByName .computeIfAbsent (metricName , unused -> new ArrayList <>());
129
+ // Skip metrics with the same name but different type
130
+ if (metricsWithHeaderName .size () > 0
131
+ && prometheusType != PrometheusType .forMetric (metricsWithHeaderName .get (0 ))) {
132
+ conflictMetricNames .add (metricName );
133
+ continue ;
134
+ }
135
+
136
+ metricsWithHeaderName .add (metric );
137
+ scopes .add (metric .getInstrumentationScopeInfo ());
138
+ }
139
+
118
140
Optional <Resource > optResource = metrics .stream ().findFirst ().map (MetricData ::getResource );
119
141
try (Writer writer =
120
142
new BufferedWriter (new OutputStreamWriter (output , StandardCharsets .UTF_8 ))) {
121
143
if (optResource .isPresent ()) {
122
144
writeResource (optResource .get (), writer );
123
145
}
124
- for (Map .Entry <InstrumentationScopeInfo , List <MetricData >> entry :
125
- metricsByScope .entrySet ()) {
146
+ for (InstrumentationScopeInfo scope : scopes ) {
147
+ writeScopeInfo (scope , writer );
148
+ }
149
+ for (Map .Entry <String , List <MetricData >> entry : metricsByName .entrySet ()) {
126
150
write (entry .getValue (), entry .getKey (), writer );
127
151
}
128
152
writeEof (writer );
129
153
}
154
+ return conflictMetricNames ;
130
155
}
131
156
132
- private void write (
133
- List <MetricData > metrics , InstrumentationScopeInfo instrumentationScopeInfo , Writer writer )
134
- throws IOException {
135
- writeScopeInfo (instrumentationScopeInfo , writer );
136
- // Group metrics with the scope, name, but different types. This is a semantic error which the
137
- // SDK warns about but passes through to exporters to handle.
138
- Map <String , List <MetricData >> metricsByName =
139
- metrics .stream ()
140
- .collect (
141
- Collectors .groupingBy (
142
- metric ->
143
- headerName (
144
- NameSanitizer .INSTANCE .apply (metric .getName ()),
145
- PrometheusType .forMetric (metric )),
146
- LinkedHashMap ::new ,
147
- Collectors .toList ()));
148
-
149
- for (Map .Entry <String , List <MetricData >> entry : metricsByName .entrySet ()) {
150
- write (entry .getValue (), entry .getKey (), writer );
151
- }
152
- }
153
-
154
- private void write (List <MetricData > metrics , String headerName , Writer writer )
157
+ private void write (List <MetricData > metrics , String metricName , Writer writer )
155
158
throws IOException {
156
159
// Write header based on first metric
157
- PrometheusType type = PrometheusType .forMetric (metrics .get (0 ));
160
+ MetricData first = metrics .get (0 );
161
+ PrometheusType type = PrometheusType .forMetric (first );
162
+ String headerName = headerName (NameSanitizer .INSTANCE .apply (first .getName ()), type );
158
163
String description = metrics .get (0 ).getDescription ();
159
164
160
165
writer .write ("# TYPE " );
@@ -171,21 +176,19 @@ private void write(List<MetricData> metrics, String headerName, Writer writer)
171
176
172
177
// Then write the metrics.
173
178
for (MetricData metric : metrics ) {
174
- write (metric , writer );
179
+ write (metric , metricName , writer );
175
180
}
176
181
}
177
182
178
- private void write (MetricData metric , Writer writer ) throws IOException {
179
- String name = metricName (metric );
180
-
183
+ private void write (MetricData metric , String metricName , Writer writer ) throws IOException {
181
184
for (PointData point : getPoints (metric )) {
182
185
switch (metric .getType ()) {
183
186
case DOUBLE_SUM :
184
187
case DOUBLE_GAUGE :
185
188
writePoint (
186
189
writer ,
187
190
metric .getInstrumentationScopeInfo (),
188
- name ,
191
+ metricName ,
189
192
((DoublePointData ) point ).getValue (),
190
193
point .getAttributes (),
191
194
point .getEpochNanos ());
@@ -195,18 +198,18 @@ private void write(MetricData metric, Writer writer) throws IOException {
195
198
writePoint (
196
199
writer ,
197
200
metric .getInstrumentationScopeInfo (),
198
- name ,
201
+ metricName ,
199
202
(double ) ((LongPointData ) point ).getValue (),
200
203
point .getAttributes (),
201
204
point .getEpochNanos ());
202
205
break ;
203
206
case HISTOGRAM :
204
207
writeHistogram (
205
- writer , metric .getInstrumentationScopeInfo (), name , (HistogramPointData ) point );
208
+ writer , metric .getInstrumentationScopeInfo (), metricName , (HistogramPointData ) point );
206
209
break ;
207
210
case SUMMARY :
208
211
writeSummary (
209
- writer , metric .getInstrumentationScopeInfo (), name , (SummaryPointData ) point );
212
+ writer , metric .getInstrumentationScopeInfo (), metricName , (SummaryPointData ) point );
210
213
break ;
211
214
case EXPONENTIAL_HISTOGRAM :
212
215
throw new IllegalArgumentException ("Can't happen" );
@@ -648,9 +651,8 @@ static Collection<? extends PointData> getPoints(MetricData metricData) {
648
651
return Collections .emptyList ();
649
652
}
650
653
651
- private static String metricName (MetricData metric ) {
652
- PrometheusType type = PrometheusType .forMetric (metric );
653
- String name = NameSanitizer .INSTANCE .apply (metric .getName ());
654
+ private static String metricName (String rawMetricName , PrometheusType type ) {
655
+ String name = NameSanitizer .INSTANCE .apply (rawMetricName );
654
656
if (type == PrometheusType .COUNTER ) {
655
657
name = name + "_total" ;
656
658
}
0 commit comments