6
6
import re
7
7
8
8
from ..metrics_core import Metric , METRIC_LABEL_NAME_RE
9
- from ..samples import Exemplar , Sample , Timestamp
9
+ from ..samples import BucketSpan , Exemplar , NativeHistogram , Sample , Timestamp
10
10
from ..utils import floatToGoString
11
11
12
12
@@ -364,6 +364,99 @@ def _parse_remaining_text(text):
364
364
return val , ts , exemplar
365
365
366
366
367
+ def _parse_nh_sample (text , suffixes ):
368
+ labels_start = text .find ("{" )
369
+ # check if it's a native histogram with labels
370
+ re_nh_without_labels = re .compile (r'^[^{} ]+ {[^{}]+}$' )
371
+ re_nh_with_labels = re .compile (r'[^{} ]+{[^{}]+} {[^{}]+}$' )
372
+ if re_nh_with_labels .match (text ):
373
+ nh_value_start = text .rindex ("{" )
374
+ labels_end = nh_value_start - 2
375
+ labelstext = text [labels_start + 1 :labels_end ]
376
+ labels = _parse_labels (labelstext )
377
+ name_end = labels_start
378
+ name = text [:name_end ]
379
+ if name .endswith (suffixes ):
380
+ raise ValueError ("the sample name of a native histogram with labels should have no suffixes" , name )
381
+ nh_value = text [nh_value_start :]
382
+ nat_hist_value = _parse_nh_struct (nh_value )
383
+ return Sample (name , labels , None , None , None , nat_hist_value )
384
+ # check if it's a native histogram
385
+ if re_nh_without_labels .match (text ):
386
+ nh_value_start = labels_start
387
+ nh_value = text [nh_value_start :]
388
+ name_end = nh_value_start - 1
389
+ name = text [:name_end ]
390
+ if name .endswith (suffixes ):
391
+ raise ValueError ("the sample name of a native histogram should have no suffixes" , name )
392
+ nat_hist_value = _parse_nh_struct (nh_value )
393
+ return Sample (name , None , None , None , None , nat_hist_value )
394
+ else :
395
+ # it's not a native histogram
396
+ return
397
+
398
+
399
+ def _parse_nh_struct (text ):
400
+ pattern = r'(\w+):\s*([^,}]+)'
401
+
402
+ re_spans = re .compile (r'(positive_spans|negative_spans):\[(\d+:\d+,\d+:\d+)\]' )
403
+ re_deltas = re .compile (r'(positive_deltas|negative_deltas):\[(-?\d+(?:,-?\d+)*)\]' )
404
+
405
+ items = dict (re .findall (pattern , text ))
406
+ spans = dict (re_spans .findall (text ))
407
+ deltas = dict (re_deltas .findall (text ))
408
+
409
+ count_value = int (items ['count' ])
410
+ sum_value = int (items ['sum' ])
411
+ schema = int (items ['schema' ])
412
+ zero_threshold = float (items ['zero_threshold' ])
413
+ zero_count = int (items ['zero_count' ])
414
+
415
+ try :
416
+ pos_spans_text = spans ['positive_spans' ]
417
+ elems = pos_spans_text .split (',' )
418
+ arg1 = [int (x ) for x in elems [0 ].split (':' )]
419
+ arg2 = [int (x ) for x in elems [1 ].split (':' )]
420
+ pos_spans = (BucketSpan (arg1 [0 ], arg1 [1 ]), BucketSpan (arg2 [0 ], arg2 [1 ]))
421
+ except KeyError :
422
+ pos_spans = None
423
+
424
+ try :
425
+ neg_spans_text = spans ['negative_spans' ]
426
+ elems = neg_spans_text .split (',' )
427
+ arg1 = [int (x ) for x in elems [0 ].split (':' )]
428
+ arg2 = [int (x ) for x in elems [1 ].split (':' )]
429
+ neg_spans = (BucketSpan (arg1 [0 ], arg1 [1 ]), BucketSpan (arg2 [0 ], arg2 [1 ]))
430
+ except KeyError :
431
+ neg_spans = None
432
+
433
+ try :
434
+ pos_deltas_text = deltas ['positive_deltas' ]
435
+ elems = pos_deltas_text .split (',' )
436
+ pos_deltas = tuple ([int (x ) for x in elems ])
437
+ except KeyError :
438
+ pos_deltas = None
439
+
440
+ try :
441
+ neg_deltas_text = deltas ['negative_deltas' ]
442
+ elems = neg_deltas_text .split (',' )
443
+ neg_deltas = tuple ([int (x ) for x in elems ])
444
+ except KeyError :
445
+ neg_deltas = None
446
+
447
+ return NativeHistogram (
448
+ count_value = count_value ,
449
+ sum_value = sum_value ,
450
+ schema = schema ,
451
+ zero_threshold = zero_threshold ,
452
+ zero_count = zero_count ,
453
+ pos_spans = pos_spans ,
454
+ neg_spans = neg_spans ,
455
+ pos_deltas = pos_deltas ,
456
+ neg_deltas = neg_deltas
457
+ )
458
+
459
+
367
460
def _group_for_sample (sample , name , typ ):
368
461
if typ == 'info' :
369
462
# We can't distinguish between groups for info metrics.
@@ -406,6 +499,8 @@ def do_checks():
406
499
for s in samples :
407
500
suffix = s .name [len (name ):]
408
501
g = _group_for_sample (s , name , 'histogram' )
502
+ if len (suffix ) == 0 :
503
+ continue
409
504
if g != group or s .timestamp != timestamp :
410
505
if group is not None :
411
506
do_checks ()
@@ -486,6 +581,8 @@ def build_metric(name, documentation, typ, unit, samples):
486
581
metric .samples = samples
487
582
return metric
488
583
584
+ is_nh = False
585
+ typ = None
489
586
for line in fd :
490
587
if line [- 1 ] == '\n ' :
491
588
line = line [:- 1 ]
@@ -518,7 +615,7 @@ def build_metric(name, documentation, typ, unit, samples):
518
615
group_timestamp_samples = set ()
519
616
samples = []
520
617
allowed_names = [parts [2 ]]
521
-
618
+
522
619
if parts [1 ] == 'HELP' :
523
620
if documentation is not None :
524
621
raise ValueError ("More than one HELP for metric: " + line )
@@ -537,8 +634,18 @@ def build_metric(name, documentation, typ, unit, samples):
537
634
else :
538
635
raise ValueError ("Invalid line: " + line )
539
636
else :
540
- sample = _parse_sample (line )
541
- if sample .name not in allowed_names :
637
+ if typ == 'histogram' :
638
+ # set to true to account for native histograms naming exceptions/sanitizing differences
639
+ is_nh = True
640
+ sample = _parse_nh_sample (line , tuple (type_suffixes ['histogram' ]))
641
+ # It's not a native histogram
642
+ if sample is None :
643
+ is_nh = False
644
+ sample = _parse_sample (line )
645
+ else :
646
+ is_nh = False
647
+ sample = _parse_sample (line )
648
+ if sample .name not in allowed_names and not is_nh :
542
649
if name is not None :
543
650
yield build_metric (name , documentation , typ , unit , samples )
544
651
# Start an unknown metric.
@@ -570,26 +677,29 @@ def build_metric(name, documentation, typ, unit, samples):
570
677
or _isUncanonicalNumber (sample .labels ['quantile' ]))):
571
678
raise ValueError ("Invalid quantile label: " + line )
572
679
573
- g = tuple (sorted (_group_for_sample (sample , name , typ ).items ()))
574
- if group is not None and g != group and g in seen_groups :
575
- raise ValueError ("Invalid metric grouping: " + line )
576
- if group is not None and g == group :
577
- if (sample .timestamp is None ) != (group_timestamp is None ):
578
- raise ValueError ("Mix of timestamp presence within a group: " + line )
579
- if group_timestamp is not None and group_timestamp > sample .timestamp and typ != 'info' :
580
- raise ValueError ("Timestamps went backwards within a group: " + line )
680
+ if not is_nh :
681
+ g = tuple (sorted (_group_for_sample (sample , name , typ ).items ()))
682
+ if group is not None and g != group and g in seen_groups :
683
+ raise ValueError ("Invalid metric grouping: " + line )
684
+ if group is not None and g == group :
685
+ if (sample .timestamp is None ) != (group_timestamp is None ):
686
+ raise ValueError ("Mix of timestamp presence within a group: " + line )
687
+ if group_timestamp is not None and group_timestamp > sample .timestamp and typ != 'info' :
688
+ raise ValueError ("Timestamps went backwards within a group: " + line )
689
+ else :
690
+ group_timestamp_samples = set ()
691
+
692
+ series_id = (sample .name , tuple (sorted (sample .labels .items ())))
693
+ if sample .timestamp != group_timestamp or series_id not in group_timestamp_samples :
694
+ # Not a duplicate due to timestamp truncation.
695
+ samples .append (sample )
696
+ group_timestamp_samples .add (series_id )
697
+
698
+ group = g
699
+ group_timestamp = sample .timestamp
700
+ seen_groups .add (g )
581
701
else :
582
- group_timestamp_samples = set ()
583
-
584
- series_id = (sample .name , tuple (sorted (sample .labels .items ())))
585
- if sample .timestamp != group_timestamp or series_id not in group_timestamp_samples :
586
- # Not a duplicate due to timestamp truncation.
587
702
samples .append (sample )
588
- group_timestamp_samples .add (series_id )
589
-
590
- group = g
591
- group_timestamp = sample .timestamp
592
- seen_groups .add (g )
593
703
594
704
if typ == 'stateset' and sample .value not in [0 , 1 ]:
595
705
raise ValueError ("Stateset samples can only have values zero and one: " + line )
@@ -606,7 +716,7 @@ def build_metric(name, documentation, typ, unit, samples):
606
716
(typ in ['histogram' , 'gaugehistogram' ] and sample .name .endswith ('_bucket' ))
607
717
or (typ in ['counter' ] and sample .name .endswith ('_total' ))):
608
718
raise ValueError ("Invalid line only histogram/gaugehistogram buckets and counters can have exemplars: " + line )
609
-
719
+
610
720
if name is not None :
611
721
yield build_metric (name , documentation , typ , unit , samples )
612
722
0 commit comments