1
0
mirror of https://github.com/prometheus/docs.git synced 2026-02-05 06:45:01 +01:00

chore(ci): add action to test abnf syntax and examples in OM2.0 spec

Action item from OpenMetrics 2.0 WG.

Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com>
This commit is contained in:
György Krajcsovits
2025-05-29 13:57:00 +02:00
parent 90377c2418
commit 91dacf3a45
3 changed files with 133 additions and 1 deletions

24
.github/workflows/openmetrics.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: OpenMetrics
on:
pull_request:
paths:
- 'docs/specs/om/open_metrics_spec_2_0.md'
jobs:
check-abnf:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python 3.x
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12.3"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install abnf
- name: Check ABNF for OpenMetrics 2.0
run: |
python3 scripts/check_openmetrics_spec.py docs/specs/om/open_metrics_spec_2_0.md

View File

@@ -399,7 +399,7 @@ Line endings MUST be signalled with line feed (\n) and MUST NOT contain carriage
An example of a complete exposition:
```
```openmetrics
# TYPE acme_http_router_request_seconds summary
# UNIT acme_http_router_request_seconds seconds
# HELP acme_http_router_request_seconds Latency though all of ACME's HTTP request router.

View File

@@ -0,0 +1,108 @@
#!/bin/env python3
#
# This script opens a markdown file containing the OpenMetrics specification,
# extracts the ABNF grammar from it, and checks if the grammar is valid.
# ABNF grammer must be enclosed in
# ```abnf
# exposition = metricset HASH SP eof [ LF ]
# ...
# ```
# code block, and the top node must be `exposition`.
# It also extracts examples from the OpenMetrics spec file and checks if they
# are valid according to the grammar.
# Exampes must be enclosed in
# ```openmetrics
# ... example content ...
# ```
# code blocks.
from abnf import Rule
import sys
class Grammar(Rule):
pass
# Start node for the OpenMetrics spec.
start_node = 'exposition'
def get_spec(filename):
with open(filename, 'r') as file:
lines = file.readlines()
spec = []
collecting = False
for line in lines:
if collecting:
if line.startswith('```'):
collecting = False
else:
spec.append(line.strip())
continue
if line.startswith('```abnf'):
if len(spec) > 0:
raise ValueError("Multiple ABNF blocks found in the file.")
collecting = True
if len(spec) == 0:
raise ValueError("No or empty ABNF block found in the file. Wanted ```abnf ... ```.")
return '\n'.join(spec)
class example:
def __init__(self, line_number, content):
self.line_number = line_number
self.content = content
class examples:
"""
Extracts examples from the OpenMetrics spec file with generator function.
"""
def __init__(self, filename):
self.file = open(filename, 'r')
self.line_number = 0
def __iter__(self):
return self
def __next__(self):
collecting = False
start_line = self.line_number
example_lines = []
for line in self.file:
self.line_number += 1
if collecting:
if line.startswith('```'):
collecting = False
break
else:
example_lines.append(line)
elif line.startswith('```openmetrics'):
start_line = self.line_number
collecting = True
if len(example_lines) > 0:
return example(start_line, ''.join(example_lines).strip())
raise StopIteration("No more examples found.")
# Main
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python3 check_openmetrics_spec.py <filename.md>")
sys.exit(1)
filename = sys.argv[1]
if not filename.endswith('.md'):
print(f"Error: {filename} is not a Markdown file.")
sys.exit(1)
spec = get_spec(filename)
try:
Grammar.load_grammar(grammar=spec, strict=True)
except Exception as e:
print(f"Error parsing ABNF: {e}")
sys.exit(1)
print("ABNF parsed successfully.")
for ex in examples(filename):
try:
Grammar.get(start_node).parse_all(ex.content)
print(f"Example parsed successfully: {ex.line_number}: {ex.content[:30]}...") # Print first 30 chars
except Exception as e:
print(f"Error parsing example at line {ex.line_number}: {e}\nExample: {ex.content[:30]}...")