@@ -228,7 +228,26 @@ def _parse_sample(text):
228228 _parse_timestamp (exemplar_timestamp ))
229229
230230 return core .Sample ('' .join (name ), labels , val , ts , exemplar )
231-
231+
232+
233+ def _group_for_sample (sample , name , typ ):
234+ if typ == 'info' :
235+ # We can't distinguish between groups for info metrics.
236+ return {}
237+ if typ == 'summary' and sample .name == name :
238+ d = sample .labels .copy ()
239+ del d ['quantile' ]
240+ return d
241+ if typ == 'stateset' :
242+ d = sample .labels .copy ()
243+ del d [name ]
244+ return d
245+ if typ in ['histogram' , 'gaugehistogram' ] and sample .name == name + '_bucket' :
246+ d = sample .labels .copy ()
247+ del d ['le' ]
248+ return d
249+ return sample .labels
250+
232251
233252def text_fd_to_metric_families (fd ):
234253 """Parse Prometheus text format from a file descriptor.
@@ -240,9 +259,11 @@ def text_fd_to_metric_families(fd):
240259 Yields core.Metric's.
241260 """
242261 name = ''
243- documentation = ''
244- typ = 'untyped'
245- unit = ''
262+ documentation = None
263+ typ = None
264+ unit = None
265+ group = None
266+ seen_groups = set ()
246267 samples = []
247268 allowed_names = []
248269 eof = False
@@ -253,7 +274,7 @@ def build_metric(name, documentation, typ, unit, samples):
253274 raise ValueError ("Duplicate metric: " + name )
254275 seen_metrics .add (name )
255276 if typ is None :
256- typ = 'untyped '
277+ typ = 'unknown '
257278 if documentation is None :
258279 documentation = ''
259280 if unit is None :
@@ -264,7 +285,7 @@ def build_metric(name, documentation, typ, unit, samples):
264285 raise ValueError ("Units not allowed for this metric type: " + name )
265286 metric = core .Metric (name , documentation , typ , unit )
266287 # TODO: check labelvalues are valid utf8
267- # TODO: check samples are appropriately grouped and ordered
288+ # TODO: check samples are appropriately ordered
268289 # TODO: Check histogram bucket rules being followed
269290 # TODO: Check for dupliate samples
270291 # TODO: Check for decresing timestamps
@@ -294,6 +315,8 @@ def build_metric(name, documentation, typ, unit, samples):
294315 unit = None
295316 typ = None
296317 documentation = None
318+ group = None
319+ seen_groups = set ()
297320 samples = []
298321 allowed_names = [parts [2 ]]
299322
@@ -308,6 +331,8 @@ def build_metric(name, documentation, typ, unit, samples):
308331 if typ is not None :
309332 raise ValueError ("More than one TYPE for metric: " + line )
310333 typ = parts [3 ]
334+ if typ == 'untyped' :
335+ raise ValueError ("Invalid TYPE for metric: " + line )
311336 allowed_names = {
312337 'counter' : ['_total' , '_created' ],
313338 'summary' : ['_count' , '_sum' , '' , '_created' ],
@@ -327,16 +352,33 @@ def build_metric(name, documentation, typ, unit, samples):
327352 if sample .name not in allowed_names :
328353 if name != '' :
329354 yield build_metric (name , documentation , typ , unit , samples )
330- # Start an untyped metric.
355+ # Start an unknown metric.
331356 name = sample .name
332- documentation = ''
333- unit = ''
334- typ = 'untyped '
357+ documentation = None
358+ unit = None
359+ typ = 'unknown '
335360 samples = [sample ]
361+ group = None
362+ seen_groups = set ()
336363 allowed_names = [sample .name ]
337364 else :
338365 samples .append (sample )
339366
367+ if typ == 'stateset' and name not in sample .labels :
368+ raise ValueError ("Stateset missing label: " + line )
369+ if (typ in ['histogram' , 'gaugehistogram' ] and name + '_bucket' == sample .name
370+ and float (sample .labels .get ('le' , - 1 )) < 0 ):
371+ raise ValueError ("Invalid le label: " + line )
372+ if (typ == 'summary' and name == sample .name
373+ and not (0 <= float (sample .labels .get ('quantile' , - 1 )) <= 1 )):
374+ raise ValueError ("Invalid quantile label: " + line )
375+
376+ g = tuple (sorted (_group_for_sample (sample , name , typ ).items ()))
377+ if group is not None and g != group and g in seen_groups :
378+ raise ValueError ("Invalid metric group ordering: " + line )
379+ group = g
380+ seen_groups .add (g )
381+
340382 if typ == 'stateset' and sample .value not in [0 , 1 ]:
341383 raise ValueError ("Stateset samples can only have values zero and one: " + line )
342384 if typ == 'info' and sample .value != 1 :
0 commit comments