36
36
import com .google .api .MetricDescriptor .MetricKind ;
37
37
import com .google .api .MetricDescriptor .ValueType ;
38
38
import com .google .api .MonitoredResource ;
39
+ import com .google .monitoring .v3 .DroppedLabels ;
39
40
import com .google .monitoring .v3 .Point ;
41
+ import com .google .monitoring .v3 .SpanContext ;
40
42
import com .google .monitoring .v3 .TimeInterval ;
41
43
import com .google .monitoring .v3 .TimeSeries ;
42
44
import com .google .monitoring .v3 .TypedValue ;
45
+ import com .google .protobuf .Any ;
46
+ import com .google .protobuf .Timestamp ;
43
47
import com .google .protobuf .util .Timestamps ;
44
48
import io .opentelemetry .api .common .AttributeKey ;
45
49
import io .opentelemetry .api .common .Attributes ;
46
50
import io .opentelemetry .sdk .metrics .data .AggregationTemporality ;
51
+ import io .opentelemetry .sdk .metrics .data .DoubleExemplarData ;
47
52
import io .opentelemetry .sdk .metrics .data .DoublePointData ;
53
+ import io .opentelemetry .sdk .metrics .data .ExemplarData ;
48
54
import io .opentelemetry .sdk .metrics .data .HistogramData ;
49
55
import io .opentelemetry .sdk .metrics .data .HistogramPointData ;
56
+ import io .opentelemetry .sdk .metrics .data .LongExemplarData ;
50
57
import io .opentelemetry .sdk .metrics .data .LongPointData ;
51
58
import io .opentelemetry .sdk .metrics .data .MetricData ;
52
59
import io .opentelemetry .sdk .metrics .data .MetricDataType ;
57
64
import java .util .List ;
58
65
import java .util .logging .Level ;
59
66
import java .util .logging .Logger ;
67
+ import java .util .stream .Collectors ;
60
68
61
69
class SpannerCloudMonitoringExporterUtils {
62
70
@@ -69,7 +77,8 @@ static String getProjectId(Resource resource) {
69
77
return resource .getAttributes ().get (PROJECT_ID_KEY );
70
78
}
71
79
72
- static List <TimeSeries > convertToSpannerTimeSeries (List <MetricData > collection ) {
80
+ static List <TimeSeries > convertToSpannerTimeSeries (
81
+ List <MetricData > collection , String projectId ) {
73
82
List <TimeSeries > allTimeSeries = new ArrayList <>();
74
83
75
84
for (MetricData metricData : collection ) {
@@ -94,7 +103,8 @@ static List<TimeSeries> convertToSpannerTimeSeries(List<MetricData> collection)
94
103
metricData .getData ().getPoints ().stream ()
95
104
.map (
96
105
pointData ->
97
- convertPointToSpannerTimeSeries (metricData , pointData , monitoredResourceBuilder ))
106
+ convertPointToSpannerTimeSeries (
107
+ metricData , pointData , monitoredResourceBuilder , projectId ))
98
108
.forEach (allTimeSeries ::add );
99
109
}
100
110
return allTimeSeries ;
@@ -103,7 +113,8 @@ static List<TimeSeries> convertToSpannerTimeSeries(List<MetricData> collection)
103
113
private static TimeSeries convertPointToSpannerTimeSeries (
104
114
MetricData metricData ,
105
115
PointData pointData ,
106
- MonitoredResource .Builder monitoredResourceBuilder ) {
116
+ MonitoredResource .Builder monitoredResourceBuilder ,
117
+ String projectId ) {
107
118
TimeSeries .Builder builder =
108
119
TimeSeries .newBuilder ()
109
120
.setMetricKind (convertMetricKind (metricData ))
@@ -135,7 +146,7 @@ private static TimeSeries convertPointToSpannerTimeSeries(
135
146
.setEndTime (Timestamps .fromNanos (pointData .getEpochNanos ()))
136
147
.build ();
137
148
138
- builder .addPoints (createPoint (metricData .getType (), pointData , timeInterval ));
149
+ builder .addPoints (createPoint (metricData .getType (), pointData , timeInterval , projectId ));
139
150
140
151
return builder .build ();
141
152
}
@@ -191,15 +202,16 @@ private static ValueType convertValueType(MetricDataType metricDataType) {
191
202
}
192
203
193
204
private static Point createPoint (
194
- MetricDataType type , PointData pointData , TimeInterval timeInterval ) {
205
+ MetricDataType type , PointData pointData , TimeInterval timeInterval , String projectId ) {
195
206
Point .Builder builder = Point .newBuilder ().setInterval (timeInterval );
196
207
switch (type ) {
197
208
case HISTOGRAM :
198
209
case EXPONENTIAL_HISTOGRAM :
199
210
return builder
200
211
.setValue (
201
212
TypedValue .newBuilder ()
202
- .setDistributionValue (convertHistogramData ((HistogramPointData ) pointData ))
213
+ .setDistributionValue (
214
+ convertHistogramData ((HistogramPointData ) pointData , projectId ))
203
215
.build ())
204
216
.build ();
205
217
case DOUBLE_GAUGE :
@@ -221,14 +233,73 @@ private static Point createPoint(
221
233
}
222
234
}
223
235
224
- private static Distribution convertHistogramData (HistogramPointData pointData ) {
236
+ private static Distribution convertHistogramData (HistogramPointData pointData , String projectId ) {
225
237
return Distribution .newBuilder ()
226
238
.setCount (pointData .getCount ())
227
239
.setMean (pointData .getCount () == 0L ? 0.0D : pointData .getSum () / pointData .getCount ())
228
240
.setBucketOptions (
229
241
BucketOptions .newBuilder ()
230
242
.setExplicitBuckets (Explicit .newBuilder ().addAllBounds (pointData .getBoundaries ())))
231
243
.addAllBucketCounts (pointData .getCounts ())
244
+ .addAllExemplars (
245
+ pointData .getExemplars ().stream ()
246
+ .map (e -> mapExemplar (e , projectId ))
247
+ .collect (Collectors .toList ()))
232
248
.build ();
233
249
}
250
+
251
+ private static Distribution .Exemplar mapExemplar (ExemplarData exemplar , String projectId ) {
252
+ double value = 0 ;
253
+ if (exemplar instanceof DoubleExemplarData ) {
254
+ value = ((DoubleExemplarData ) exemplar ).getValue ();
255
+ } else if (exemplar instanceof LongExemplarData ) {
256
+ value = ((LongExemplarData ) exemplar ).getValue ();
257
+ }
258
+
259
+ Distribution .Exemplar .Builder exemplarBuilder =
260
+ Distribution .Exemplar .newBuilder ()
261
+ .setValue (value )
262
+ .setTimestamp (mapTimestamp (exemplar .getEpochNanos ()));
263
+ if (exemplar .getSpanContext ().isValid ()) {
264
+ exemplarBuilder .addAttachments (
265
+ Any .pack (
266
+ SpanContext .newBuilder ()
267
+ .setSpanName (
268
+ makeSpanName (
269
+ projectId ,
270
+ exemplar .getSpanContext ().getTraceId (),
271
+ exemplar .getSpanContext ().getSpanId ()))
272
+ .build ()));
273
+ }
274
+ if (!exemplar .getFilteredAttributes ().isEmpty ()) {
275
+ exemplarBuilder .addAttachments (
276
+ Any .pack (mapFilteredAttributes (exemplar .getFilteredAttributes ())));
277
+ }
278
+ return exemplarBuilder .build ();
279
+ }
280
+
281
+ static final long NANO_PER_SECOND = (long ) 1e9 ;
282
+
283
+ private static Timestamp mapTimestamp (long epochNanos ) {
284
+ return Timestamp .newBuilder ()
285
+ .setSeconds (epochNanos / NANO_PER_SECOND )
286
+ .setNanos ((int ) (epochNanos % NANO_PER_SECOND ))
287
+ .build ();
288
+ }
289
+
290
+ private static String makeSpanName (String projectId , String traceId , String spanId ) {
291
+ return String .format ("projects/%s/traces/%s/spans/%s" , projectId , traceId , spanId );
292
+ }
293
+
294
+ private static DroppedLabels mapFilteredAttributes (Attributes attributes ) {
295
+ DroppedLabels .Builder labels = DroppedLabels .newBuilder ();
296
+ attributes .forEach ((k , v ) -> labels .putLabel (cleanAttributeKey (k .getKey ()), v .toString ()));
297
+ return labels .build ();
298
+ }
299
+
300
+ private static String cleanAttributeKey (String key ) {
301
+ // . is commonly used in OTel but disallowed in GCM label names,
302
+ // https://cloud.google.com/monitoring/api/ref_v3/rest/v3/LabelDescriptor#:~:text=Matches%20the%20following%20regular%20expression%3A
303
+ return key .replace ('.' , '_' );
304
+ }
234
305
}
0 commit comments