6
6
from socketserver import ThreadingMixIn
7
7
import sys
8
8
import threading
9
+ from typing import Any , Callable , Dict , List , Optional , Sequence , Tuple
9
10
from urllib .error import HTTPError
10
11
from urllib .parse import parse_qs , quote_plus , urlparse
11
12
from urllib .request import (
14
15
from wsgiref .simple_server import make_server , WSGIRequestHandler , WSGIServer
15
16
16
17
from .openmetrics import exposition as openmetrics
17
- from .registry import REGISTRY
18
+ from .registry import CollectorRegistry , REGISTRY
18
19
from .utils import floatToGoString
19
20
20
21
__all__ = (
@@ -101,7 +102,7 @@ def _bake_output(registry, accept_header, params):
101
102
return '200 OK' , ('Content-Type' , content_type ), output
102
103
103
104
104
- def make_wsgi_app (registry = REGISTRY ):
105
+ def make_wsgi_app (registry : CollectorRegistry = REGISTRY ) -> Callable :
105
106
"""Create a WSGI app which serves the metrics from a registry."""
106
107
107
108
def prometheus_app (environ , start_response ):
@@ -149,7 +150,7 @@ def _get_best_family(address, port):
149
150
return family , sockaddr [0 ]
150
151
151
152
152
- def start_wsgi_server (port , addr = '0.0.0.0' , registry = REGISTRY ):
153
+ def start_wsgi_server (port : int , addr : str = '0.0.0.0' , registry : CollectorRegistry = REGISTRY ) -> None :
153
154
"""Starts a WSGI server for prometheus metrics as a daemon thread."""
154
155
class TmpServer (ThreadingWSGIServer ):
155
156
"""Copy of ThreadingWSGIServer to update address_family locally"""
@@ -164,7 +165,7 @@ class TmpServer(ThreadingWSGIServer):
164
165
start_http_server = start_wsgi_server
165
166
166
167
167
- def generate_latest (registry = REGISTRY ):
168
+ def generate_latest (registry : CollectorRegistry = REGISTRY ) -> bytes :
168
169
"""Returns the metrics from the registry in latest text format as a string."""
169
170
170
171
def sample_line (line ):
@@ -205,7 +206,7 @@ def sample_line(line):
205
206
mname , metric .documentation .replace ('\\ ' , r'\\' ).replace ('\n ' , r'\n' )))
206
207
output .append (f'# TYPE { mname } { mtype } \n ' )
207
208
208
- om_samples = {}
209
+ om_samples : Dict [ str , List [ str ]] = {}
209
210
for s in metric .samples :
210
211
for suffix in ['_created' , '_gsum' , '_gcount' ]:
211 212
if s .name == metric .name + suffix :
@@ -226,7 +227,7 @@ def sample_line(line):
226
227
return '' .join (output ).encode ('utf-8' )
227
228
228
229
229
- def choose_encoder (accept_header ) :
230
+ def choose_encoder (accept_header : str ) -> Tuple [ Callable [[ CollectorRegistry ], bytes ], str ] :
230
231
accept_header = accept_header or ''
231
232
for accepted in accept_header .split (',' ):
232
233
if accepted .split (';' )[0 ].strip () == 'application/openmetrics-text' :
@@ -237,9 +238,9 @@ def choose_encoder(accept_header):
237
238
238
239
class MetricsHandler (BaseHTTPRequestHandler ):
239
240
"""HTTP handler that gives metrics from ``REGISTRY``."""
240
- registry = REGISTRY
241
+ registry : CollectorRegistry = REGISTRY
241
242
242
- def do_GET (self ):
243
+ def do_GET (self ) -> None :
243
244
# Prepare parameters
244
245
registry = self .registry
245
246
accept_header = self .headers .get ('Accept' )
@@ -252,11 +253,11 @@ def do_GET(self):
252
253
self .end_headers ()
253
254
self .wfile .write (output )
254
255
255
- def log_message (self , format , * args ) :
256
+ def log_message (self , format : str , * args : Any ) -> None :
256
257
"""Log nothing."""
257
258
258
259
@classmethod
259
- def factory (cls , registry ) :
260
+ def factory (cls , registry : CollectorRegistry ) -> type :
260
261
"""Returns a dynamic MetricsHandler class tied
261
262
to the passed registry.
262
263
"""
@@ -271,27 +272,34 @@ def factory(cls, registry):
271
272
return MyMetricsHandler
272
273
273
274
274
- def write_to_textfile (path , registry ) :
275
+ def write_to_textfile (path : str , registry : CollectorRegistry ) -> None :
275
276
"""Write metrics to the given path.
276
277
277
278
This is intended for use with the Node exporter textfile collector.
278
279
The path must end in .prom for the textfile collector to process it."""
279
280
tmppath = f'{ path } .{ os .getpid ()} .{ threading .current_thread ().ident } '
280
281
with open (tmppath , 'wb' ) as f :
281
282
f .write (generate_latest (registry ))
282
-
283
+
283
284
# rename(2) is atomic but fails on Windows if the destination file exists
284
285
if os .name == 'nt' :
285
286
os .replace (tmppath , path )
286
287
else :
287
288
os .rename (tmppath , path )
288
289
289
290
290
- def _make_handler (url , method , timeout , headers , data , base_handler ):
291
+ def _make_handler (
292
+ url : str ,
293
+ method : str ,
294
+ timeout : Optional [float ],
295
+ headers : Sequence [Tuple [str , str ]],
296
+ data : bytes ,
297
+ base_handler : type ,
298
+ ) -> Callable [[], None ]:
291
299
292
- def handle ():
300
+ def handle () -> None :
293
301
request = Request (url , data = data )
294
- request .get_method = lambda : method
302
+ request .get_method = lambda : method # type: ignore
295
303
for k , v in headers :
296
304
request .add_header (k , v )
297
305
resp = build_opener (base_handler ).open (request , timeout = timeout )
@@ -301,15 +309,27 @@ def handle():
301
309
return handle
302
310
303
311
304
- def default_handler (url , method , timeout , headers , data ):
312
+ def default_handler (
313
+ url : str ,
314
+ method : str ,
315
+ timeout : Optional [float ],
316
+ headers : List [Tuple [str , str ]],
317
+ data : bytes ,
318
+ ) -> Callable [[], None ]:
305
319
"""Default handler that implements HTTP/HTTPS connections.
306
320
307
321
Used by the push_to_gateway functions. Can be re-used by other handlers."""
308
322
309
323
return _make_handler (url , method , timeout , headers , data , HTTPHandler )
310
324
311
325
312
- def passthrough_redirect_handler (url , method , timeout , headers , data ):
326
+ def passthrough_redirect_handler (
327
+ url : str ,
328
+ method : str ,
329
+ timeout : Optional [float ],
330
+ headers : List [Tuple [str , str ]],
331
+ data : bytes ,
332
+ ) -> Callable [[], None ]:
313
333
"""
314
334
Handler that automatically trusts redirect responses for all HTTP methods.
315
335
@@ -323,7 +343,15 @@ def passthrough_redirect_handler(url, method, timeout, headers, data):
323
343
return _make_handler (url , method , timeout , headers , data , _PrometheusRedirectHandler )
324
344
325
345
326
- def basic_auth_handler (url , method , timeout , headers , data , username = None , password = None ):
346
+ def basic_auth_handler (
347
+ url : str ,
348
+ method : str ,
349
+ timeout : Optional [float ],
350
+ headers : List [Tuple [str , str ]],
351
+ data : bytes ,
352
+ username : str = None ,
353
+ password : str = None ,
354
+ ) -> Callable [[], None ]:
327
355
"""Handler that implements HTTP/HTTPS connections with Basic Auth.
328
356
329
357
Sets auth headers using supplied 'username' and 'password', if set.
@@ -336,15 +364,20 @@ def handle():
336
364
auth_value = f'{ username } :{ password } ' .encode ()
337
365
auth_token = base64 .b64encode (auth_value )
338
366
auth_header = b'Basic ' + auth_token
339
- headers .append ([ 'Authorization' , auth_header ] )
367
+ headers .append (( 'Authorization' , auth_header ) )
340
368
default_handler (url , method , timeout , headers , data )()
341
369
342
370
return handle
343
371
344
372
345
373
def push_to_gateway (
346
- gateway , job , registry , grouping_key = None , timeout = 30 ,
347
- handler = default_handler ):
374
+ gateway : str ,
375
+ job : str ,
376
+ registry : CollectorRegistry ,
377
+ grouping_key : Optional [Dict [str , Any ]] = None ,
378
+ timeout : Optional [float ] = 30 ,
379
+ handler : Callable = default_handler ,
380
+ ) -> None :
348
381
"""Push metrics to the given pushgateway.
349
382
350
383
`gateway` the url for your push gateway. Either of the form
@@ -387,8 +420,13 @@ def push_to_gateway(
387
420
388
421
389
422
def pushadd_to_gateway (
390
- gateway , job , registry , grouping_key = None , timeout = 30 ,
391
- handler = default_handler ):
423
+ gateway : str ,
424
+ job : str ,
425
+ registry : Optional [CollectorRegistry ],
426
+ grouping_key : Optional [Dict [str , Any ]] = None ,
427
+ timeout : Optional [float ] = 30 ,
428
+ handler : Callable = default_handler ,
429
+ ) -> None :
392
430
"""PushAdd metrics to the given pushgateway.
393
431
394
432
`gateway` the url for your push gateway. Either of the form
@@ -413,7 +451,12 @@ def pushadd_to_gateway(
413
451
414
452
415
453
def delete_from_gateway (
416
- gateway , job , grouping_key = None , timeout = 30 , handler = default_handler ):
454
+ gateway : str ,
455
+ job : str ,
456
+ grouping_key : Optional [Dict [str , Any ]] = None ,
457
+ timeout : Optional [float ] = 30 ,
458
+ handler : Callable = default_handler ,
459
+ ) -> None :
417
460
"""Delete metrics from the given pushgateway.
418
461
419
462
`gateway` the url for your push gateway. Either of the form
@@ -436,7 +479,15 @@ def delete_from_gateway(
436
479
_use_gateway ('DELETE' , gateway , job , None , grouping_key , timeout , handler )
437
480
438
481
439
- def _use_gateway (method , gateway , job , registry , grouping_key , timeout , handler ):
482
+ def _use_gateway (
483
+ method : str ,
484
+ gateway : str ,
485
+ job : str ,
486
+ registry : Optional [CollectorRegistry ],
487
+ grouping_key : Optional [Dict [str , Any ]],
488
+ timeout : Optional [float ],
489
+ handler : Callable ,
490
+ ) -> None :
440
491
gateway_url = urlparse (gateway )
441
492
# See https://bugs.python.org/issue27657 for details on urlparse in py>=3.7.6.
442
493
if not gateway_url .scheme or (
@@ -450,6 +501,8 @@ def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler)
450
501
451
502
data = b''
452
503
if method != 'DELETE' :
504
+ if registry is None :
505
+ registry = REGISTRY
453
506
data = generate_latest (registry )
454
507
455
508
if grouping_key is None :
@@ -475,7 +528,7 @@ def _escape_grouping_key(k, v):
475
528
return k , quote_plus (v )
476
529
477
530
478
- def instance_ip_grouping_key ():
531
+ def instance_ip_grouping_key () -> Dict [ str , Any ] :
479
532
"""Grouping key with instance set to the IP Address of this host."""
480
533
with closing (socket .socket (socket .AF_INET , socket .SOCK_DGRAM )) as s :
481
534
if sys .platform == 'darwin' :
0 commit comments