1515 # Python 3
1616 import io as StringIO
1717
18+
1819def text_string_to_metric_families (text ):
1920 """Parse Openmetrics text format from a unicode string.
2021
@@ -24,7 +25,7 @@ def text_string_to_metric_families(text):
2425 yield metric_family
2526
2627
27- _CANONICAL_NUMBERS = set ([i / 1000.0 for i in range ( 10000 )] + [ 10.0 ** i for i in range ( - 10 , 11 )] + [ float ("inf" )])
28+ _CANONICAL_NUMBERS = set ([float ("inf" )])
2829
2930
3031def _isUncanonicalNumber (s ):
@@ -113,8 +114,8 @@ def _parse_timestamp(timestamp):
113114
114115def _is_character_escaped (s , charpos ):
115116 num_bslashes = 0
116- while (charpos > num_bslashes and
117- s [charpos - 1 - num_bslashes ] == '\\ ' ):
117+ while (charpos > num_bslashes
118+ and s [charpos - 1 - num_bslashes ] == '\\ ' ):
118119 num_bslashes += 1
119120 return num_bslashes % 2 == 1
120121
@@ -359,7 +360,7 @@ def _parse_remaining_text(text):
359360 exemplar = None
360361 if exemplar_labels is not None :
361362 exemplar_length = sum ([len (k ) + len (v ) for k , v in exemplar_labels .items ()])
362- if exemplar_length > 64 :
363+ if exemplar_length > 128 :
363364 raise ValueError ("Exmplar labels are too long: " + text )
364365 exemplar = Exemplar (
365366 exemplar_labels ,
@@ -398,6 +399,12 @@ def do_checks():
398399 raise ValueError ("+Inf bucket missing: " + name )
399400 if count is not None and value != count :
400401 raise ValueError ("Count does not match +Inf value: " + name )
402+ if has_sum and count is None :
403+ raise ValueError ("_count must be present if _sum is present: " + name )
404+ if has_gsum and count is None :
405+ raise ValueError ("_gcount must be present if _gsum is present: " + name )
406+ if not (has_sum or has_gsum ) and count is not None :
407+ raise ValueError ("_sum/_gsum must be present if _count is present: " + name )
401408 if has_negative_buckets and has_sum :
402409 raise ValueError ("Cannot have _sum with negative buckets: " + name )
403410 if not has_negative_buckets and has_negative_gsum :
@@ -413,6 +420,7 @@ def do_checks():
413420 bucket = None
414421 has_negative_buckets = False
415422 has_sum = False
423+ has_gsum = False
416424 has_negative_gsum = False
417425 value = 0
418426 group = g
@@ -432,8 +440,10 @@ def do_checks():
432440 count = s .value
433441 elif suffix in ['_sum' ]:
434442 has_sum = True
435- elif suffix in ['_gsum' ] and s .value < 0 :
436- has_negative_gsum = True
443+ elif suffix in ['_gsum' ]:
444+ has_gsum = True
445+ if s .value < 0 :
446+ has_negative_gsum = True
437447
438448 if group is not None :
439449 do_checks ()
@@ -452,14 +462,22 @@ def text_fd_to_metric_families(fd):
452462 allowed_names = []
453463 eof = False
454464
455- seen_metrics = set ()
465+ seen_names = set ()
466+ type_suffixes = {
467+ 'counter' : ['_total' , '_created' ],
468+ 'summary' : ['' , '_count' , '_sum' , '_created' ],
469+ 'histogram' : ['_count' , '_sum' , '_bucket' , '_created' ],
470+ 'gaugehistogram' : ['_gcount' , '_gsum' , '_bucket' ],
471+ 'info' : ['_info' ],
472+ }
456473
457474 def build_metric (name , documentation , typ , unit , samples ):
458- if name in seen_metrics :
459- raise ValueError ("Duplicate metric: " + name )
460- seen_metrics .add (name )
461475 if typ is None :
462476 typ = 'unknown'
477+ for suffix in set (type_suffixes .get (typ , []) + ["" ]):
478+ if name + suffix in seen_names :
479+ raise ValueError ("Clashing name: " + name + suffix )
480+ seen_names .add (name + suffix )
463481 if documentation is None :
464482 documentation = ''
465483 if unit is None :
@@ -482,6 +500,9 @@ def build_metric(name, documentation, typ, unit, samples):
482500 if eof :
483501 raise ValueError ("Received line after # EOF: " + line )
484502
503+ if not line :
504+ raise ValueError ("Received blank line" )
505+
485506 if line == '# EOF' :
486507 eof = True
487508 elif line .startswith ('#' ):
@@ -518,14 +539,7 @@ def build_metric(name, documentation, typ, unit, samples):
518539 typ = parts [3 ]
519540 if typ == 'untyped' :
520541 raise ValueError ("Invalid TYPE for metric: " + line )
521- allowed_names = {
522- 'counter' : ['_total' , '_created' ],
523- 'summary' : ['_count' , '_sum' , '' , '_created' ],
524- 'histogram' : ['_count' , '_sum' , '_bucket' , '_created' ],
525- 'gaugehistogram' : ['_gcount' , '_gsum' , '_bucket' ],
526- 'info' : ['_info' ],
527- }.get (typ , ['' ])
528- allowed_names = [name + n for n in allowed_names ]
542+ allowed_names = [name + n for n in type_suffixes .get (typ , ['' ])]
529543 elif parts [1 ] == 'UNIT' :
530544 if unit is not None :
531545 raise ValueError ("More than one UNIT for metric: " + line )
@@ -557,7 +571,7 @@ def build_metric(name, documentation, typ, unit, samples):
557571 raise ValueError ("Invalid le label: " + line )
558572 if (typ == 'summary' and name == sample .name
559573 and (not (0 <= float (sample .labels .get ('quantile' , - 1 )) <= 1 )
560- or _isUncanonicalNumber (sample .labels ['quantile' ]))):
574+ or _isUncanonicalNumber (sample .labels ['quantile' ]))):
561575 raise ValueError ("Invalid quantile label: " + line )
562576
563577 g = tuple (sorted (_group_for_sample (sample , name , typ ).items ()))
0 commit comments