@@ -228,7 +228,26 @@ def _parse_sample(text):
228
228
_parse_timestamp (exemplar_timestamp ))
229
229
230
230
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
+
232
251
233
252
def text_fd_to_metric_families (fd ):
234
253
"""Parse Prometheus text format from a file descriptor.
@@ -240,9 +259,11 @@ def text_fd_to_metric_families(fd):
240
259
Yields core.Metric's.
241
260
"""
242
261
name = ''
243
- documentation = ''
244
- typ = 'untyped'
245
- unit = ''
262
+ documentation = None
263
+ typ = None
264
+ unit = None
265
+ group = None
266
+ seen_groups = set ()
246
267
samples = []
247
268
allowed_names = []
248
269
eof = False
@@ -253,7 +274,7 @@ def build_metric(name, documentation, typ, unit, samples):
253
274
raise ValueError ("Duplicate metric: " + name )
254
275
seen_metrics .add (name )
255
276
if typ is None :
256
- typ = 'untyped '
277
+ typ = 'unknown '
257
278
if documentation is None :
258
279
documentation = ''
259
280
if unit is None :
@@ -264,7 +285,7 @@ def build_metric(name, documentation, typ, unit, samples):
264
285
raise ValueError ("Units not allowed for this metric type: " + name )
265
286
metric = core .Metric (name , documentation , typ , unit )
266
287
# TODO: check labelvalues are valid utf8
267
- # TODO: check samples are appropriately grouped and ordered
288
+ # TODO: check samples are appropriately ordered
268
289
# TODO: Check histogram bucket rules being followed
269
290
# TODO: Check for dupliate samples
270
291
# TODO: Check for decresing timestamps
@@ -294,6 +315,8 @@ def build_metric(name, documentation, typ, unit, samples):
294
315
unit = None
295
316
typ = None
296
317
documentation = None
318
+ group = None
319
+ seen_groups = set ()
297
320
samples = []
298
321
allowed_names = [parts [2 ]]
299
322
@@ -308,6 +331,8 @@ def build_metric(name, documentation, typ, unit, samples):
308
331
if typ is not None :
309
332
raise ValueError ("More than one TYPE for metric: " + line )
310
333
typ = parts [3 ]
334
+ if typ == 'untyped' :
335
+ raise ValueError ("Invalid TYPE for metric: " + line )
311
336
allowed_names = {
312
337
'counter' : ['_total' , '_created' ],
313
338
'summary' : ['_count' , '_sum' , '' , '_created' ],
@@ -327,16 +352,33 @@ def build_metric(name, documentation, typ, unit, samples):
327
352
if sample .name not in allowed_names :
328
353
if name != '' :
329
354
yield build_metric (name , documentation , typ , unit , samples )
330
- # Start an untyped metric.
355
+ # Start an unknown metric.
331
356
name = sample .name
332
- documentation = ''
333
- unit = ''
334
- typ = 'untyped '
357
+ documentation = None
358
+ unit = None
359
+ typ = 'unknown '
335
360
samples = [sample ]
361
+ group = None
362
+ seen_groups = set ()
336
363
allowed_names = [sample .name ]
337
364
else :
338
365
samples .append (sample )
339
366
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
+
340
382
if typ == 'stateset' and sample .value not in [0 , 1 ]:
341
383
raise ValueError ("Stateset samples can only have values zero and one: " + line )
342
384
if typ == 'info' and sample .value != 1 :
0 commit comments