14
14
15
15
import logging
16
16
import re
17
- from typing import Dict , Sequence
18
-
19
17
from collections import defaultdict
20
18
from itertools import chain
19
+ from typing import Dict , Sequence
20
+
21
21
import requests
22
22
import snappy
23
23
29
29
Sample ,
30
30
TimeSeries ,
31
31
)
32
+ from opentelemetry .sdk .metrics import Counter
33
+ from opentelemetry .sdk .metrics import Histogram as ClientHistogram
34
+ from opentelemetry .sdk .metrics import (
35
+ ObservableCounter ,
36
+ ObservableGauge ,
37
+ ObservableUpDownCounter ,
38
+ UpDownCounter ,
39
+ )
32
40
from opentelemetry .sdk .metrics .export import (
33
41
AggregationTemporality ,
34
42
Gauge ,
35
- Sum ,
36
43
Histogram ,
44
+ Metric ,
37
45
MetricExporter ,
38
46
MetricExportResult ,
39
47
MetricsData ,
40
- Metric ,
41
- )
42
- from opentelemetry .sdk .metrics import (
43
- Counter ,
44
- Histogram as ClientHistogram ,
45
- ObservableCounter ,
46
- ObservableGauge ,
47
- ObservableUpDownCounter ,
48
- UpDownCounter ,
48
+ Sum ,
49
49
)
50
50
51
51
logger = logging .getLogger (__name__ )
52
52
53
- PROMETHEUS_NAME_REGEX = re .compile (r'[^\w:]' )
54
- PROMETHEUS_LABEL_REGEX = re .compile (r'[^\w]' )
53
+ PROMETHEUS_NAME_REGEX = re .compile (r"[^\w:]" )
54
+ PROMETHEUS_LABEL_REGEX = re .compile (r"[^\w]" )
55
+
55
56
56
57
class PrometheusRemoteWriteMetricsExporter (MetricExporter ):
57
58
"""
@@ -74,7 +75,7 @@ def __init__(
74
75
timeout : int = 30 ,
75
76
tls_config : Dict = None ,
76
77
proxies : Dict = None ,
77
- resources_as_labels : bool = True ,
78
+ resources_as_labels : bool = True ,
78
79
preferred_temporality : Dict [type , AggregationTemporality ] = None ,
79
80
preferred_aggregation : Dict = None ,
80
81
):
@@ -95,9 +96,8 @@ def __init__(
95
96
ObservableUpDownCounter : AggregationTemporality .CUMULATIVE ,
96
97
ObservableGauge : AggregationTemporality .CUMULATIVE ,
97
98
}
98
- logger .error ("Calling MetricExporter" )
99
99
100
- super ().__init__ (preferred_temporality ,preferred_aggregation )
100
+ super ().__init__ (preferred_temporality , preferred_aggregation )
101
101
102
102
@property
103
103
def endpoint (self ):
@@ -180,9 +180,9 @@ def headers(self, headers: Dict):
180
180
181
181
def export (
182
182
self ,
183
- metrics_data : MetricsData ,
183
+ metrics_data : MetricsData ,
184
184
timeout_millis : float = 10_000 ,
185
- ) -> MetricExportResult :
185
+ ) -> MetricExportResult :
186
186
if not metrics_data :
187
187
return MetricExportResult .SUCCESS
188
188
timeseries = self ._translate_data (metrics_data )
@@ -203,121 +203,129 @@ def _translate_data(self, data: MetricsData) -> Sequence[TimeSeries]:
203
203
# OTLP Data model suggests combining some attrs into job/instance
204
204
# Should we do that here?
205
205
if self .resources_as_labels :
206
- resource_labels = [ (n ,str (v )) for n ,v in resource .attributes .items () ]
206
+ resource_labels = [
207
+ (n , str (v )) for n , v in resource .attributes .items ()
208
+ ]
207
209
else :
208
210
resource_labels = []
209
211
# Scope name/version probably not too useful from a labeling perspective
210
212
for scope_metrics in resource_metrics .scope_metrics :
211
213
for metric in scope_metrics .metrics :
212
- rw_timeseries .extend ( self ._parse_metric (metric ,resource_labels ) )
214
+ rw_timeseries .extend (
215
+ self ._parse_metric (metric , resource_labels )
216
+ )
213
217
return rw_timeseries
214
218
215
- def _parse_metric (self , metric : Metric , resource_labels : Sequence ) -> Sequence [TimeSeries ]:
219
+ def _parse_metric (
220
+ self , metric : Metric , resource_labels : Sequence
221
+ ) -> Sequence [TimeSeries ]:
216
222
"""
217
223
Parses the Metric & lower objects, then converts the output into
218
224
OM TimeSeries. Returns a List of TimeSeries objects based on one Metric
219
225
"""
220
226
221
-
222
227
# Create the metric name, will be a label later
223
228
if metric .unit :
224
- #Prom. naming guidelines add unit to the name
225
- name = f"{ metric .name } _{ metric .unit } "
229
+ # Prom. naming guidelines add unit to the name
230
+ name = f"{ metric .name } _{ metric .unit } "
226
231
else :
227
232
name = metric .name
228
233
229
234
# datapoints have attributes associated with them. these would be sent
230
235
# to RW as different metrics: name & labels is a unique time series
231
236
sample_sets = defaultdict (list )
232
- if isinstance (metric .data ,(Gauge ,Sum )):
237
+ if isinstance (metric .data , (Gauge , Sum )):
233
238
for dp in metric .data .data_points :
234
- attrs ,sample = self ._parse_data_point (dp ,name )
239
+ attrs , sample = self ._parse_data_point (dp , name )
235
240
sample_sets [attrs ].append (sample )
236
- elif isinstance (metric .data ,Histogram ):
241
+ elif isinstance (metric .data , Histogram ):
237
242
for dp in metric .data .data_points :
238
- dp_result = self ._parse_histogram_data_point (dp ,name )
239
- for attrs ,sample in dp_result :
243
+ dp_result = self ._parse_histogram_data_point (dp , name )
244
+ for attrs , sample in dp_result :
240
245
sample_sets [attrs ].append (sample )
241
246
else :
242
- logger .warn ("Unsupported Metric Type: %s" ,type (metric .data ))
247
+ logger .warn ("Unsupported Metric Type: %s" , type (metric .data ))
243
248
return []
244
249
245
250
timeseries = []
246
251
for labels , samples in sample_sets .items ():
247
252
ts = TimeSeries ()
248
- for label_name ,label_value in chain (resource_labels ,labels ):
253
+ for label_name , label_value in chain (resource_labels , labels ):
249
254
# Previous implementation did not str() the names...
250
- ts .labels .append (self ._label (label_name ,str (label_value )))
251
- for value ,timestamp in samples :
252
- ts .samples .append (self ._sample (value ,timestamp ))
255
+ ts .labels .append (self ._label (label_name , str (label_value )))
256
+ for value , timestamp in samples :
257
+ ts .samples .append (self ._sample (value , timestamp ))
253
258
timeseries .append (ts )
254
259
return timeseries
255
260
256
- def _sample (self ,value : int ,timestamp : int ) -> Sample :
261
+ def _sample (self , value : int , timestamp : int ) -> Sample :
257
262
sample = Sample ()
258
263
sample .value = value
259
264
sample .timestamp = timestamp
260
265
return sample
261
266
262
- def _label (self ,name :str ,value :str ) -> Label :
267
+ def _label (self , name : str , value : str ) -> Label :
263
268
label = Label ()
264
- label .name = PROMETHEUS_LABEL_REGEX .sub ("_" ,name )
269
+ label .name = PROMETHEUS_LABEL_REGEX .sub ("_" , name )
265
270
label .value = value
266
271
return label
267
272
268
- def _sanitize_name (self ,name ):
273
+ def _sanitize_name (self , name ):
269
274
# I Think Prometheus requires names to NOT start with a number this
270
275
# would not catch that, but do cover the other cases. The naming rules
271
276
# don't explicit say this, but the supplied regex implies it.
272
277
# Got a little weird trying to do substitution with it, but can be
273
278
# fixed if we allow numeric beginnings to metric names
274
- return PROMETHEUS_NAME_REGEX .sub ("_" ,name )
279
+ return PROMETHEUS_NAME_REGEX .sub ("_" , name )
275
280
276
281
def _parse_histogram_data_point (self , data_point , name ):
277
282
278
- #if (len(data_point.explicit_bounds)+1) != len(data_point.bucket_counts):
283
+ # if (len(data_point.explicit_bounds)+1) != len(data_point.bucket_counts):
279
284
# raise ValueError("Number of buckets must be 1 more than the explicit bounds!")
280
285
281
286
sample_attr_pairs = []
282
287
283
- base_attrs = [(n ,v ) for n ,v in data_point .attributes .items ()]
288
+ base_attrs = [(n , v ) for n , v in data_point .attributes .items ()]
284
289
timestamp = data_point .time_unix_nano // 1_000_000
285
290
286
-
287
- def handle_bucket (value ,bound = None ,name_override = None ):
291
+ def handle_bucket (value , bound = None , name_override = None ):
288
292
# Metric Level attributes + the bucket boundry attribute + name
289
293
ts_attrs = base_attrs .copy ()
290
- ts_attrs .append (("__name__" ,self ._sanitize_name (name_override or name )))
294
+ ts_attrs .append (
295
+ ("__name__" , self ._sanitize_name (name_override or name ))
296
+ )
291
297
if bound :
292
- ts_attrs .append (("le" ,str (bound )))
298
+ ts_attrs .append (("le" , str (bound )))
293
299
# Value is count of values in each bucket
294
- ts_sample = (value ,timestamp )
300
+ ts_sample = (value , timestamp )
295
301
return tuple (ts_attrs ), ts_sample
296
302
297
- for bound_pos ,bound in enumerate (data_point .explicit_bounds ):
303
+ for bound_pos , bound in enumerate (data_point .explicit_bounds ):
298
304
sample_attr_pairs .append (
299
- handle_bucket (data_point .bucket_counts [bound_pos ],bound )
305
+ handle_bucket (data_point .bucket_counts [bound_pos ], bound )
300
306
)
301
307
302
308
# Add the last label for implicit +inf bucket
303
309
sample_attr_pairs .append (
304
- handle_bucket (data_point .bucket_counts [- 1 ],bound = "+Inf" )
310
+ handle_bucket (data_point .bucket_counts [- 1 ], bound = "+Inf" )
305
311
)
306
312
307
- #Lastly, add series for count & sum
313
+ # Lastly, add series for count & sum
308
314
sample_attr_pairs .append (
309
- handle_bucket (data_point .sum ,name_override = f"{ name } _sum" )
315
+ handle_bucket (data_point .sum , name_override = f"{ name } _sum" )
310
316
)
311
317
sample_attr_pairs .append (
312
- handle_bucket (data_point .count ,name_override = f"{ name } _count" )
318
+ handle_bucket (data_point .count , name_override = f"{ name } _count" )
313
319
)
314
320
return sample_attr_pairs
315
321
316
- def _parse_data_point (self , data_point ,name = None ):
322
+ def _parse_data_point (self , data_point , name = None ):
317
323
318
- attrs = tuple (data_point .attributes .items ()) + (("__name__" ,self ._sanitize_name (name )),)
319
- sample = (data_point .value ,(data_point .time_unix_nano // 1_000_000 ))
320
- return attrs ,sample
324
+ attrs = tuple (data_point .attributes .items ()) + (
325
+ ("__name__" , self ._sanitize_name (name )),
326
+ )
327
+ sample = (data_point .value , (data_point .time_unix_nano // 1_000_000 ))
328
+ return attrs , sample
321
329
322
330
# pylint: disable=no-member,no-self-use
323
331
def _build_message (self , timeseries : Sequence [TimeSeries ]) -> bytes :
@@ -383,4 +391,3 @@ def force_flush(self, timeout_millis: float = 10_000) -> bool:
383
391
384
392
def shutdown (self ) -> None :
385
393
pass
386
-
0 commit comments