Skip to content

Commit 2a2ca52

Browse files
committed
Avoid unnecessary iterating across the same term
Split the term into the label name and label value portions in one swoop rather than starting from the beginning to find an = character after already going through the full term. This saves ~5% on the benchmark. Signed-off-by: Chris Marchbanks <[email protected]>
1 parent 119f1c2 commit 2a2ca52

File tree

1 file changed

+27
-29
lines changed

1 file changed

+27
-29
lines changed

prometheus_client/parser.py

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -62,44 +62,35 @@ def parse_labels(labels_string: str, openmetrics: bool = False) -> Dict[str, str
6262
# The label name is before the equal, or if there's no equal, that's the
6363
# metric name.
6464

65-
term, sub_labels = _next_term(sub_labels, openmetrics)
66-
if not term:
65+
name_term, value_term, sub_labels = _next_term(sub_labels, openmetrics)
66+
if not value_term:
6767
if openmetrics:
6868
raise ValueError("empty term in line: " + labels_string)
6969
continue
7070

71-
quoted_name = False
72-
operator_pos = _next_unquoted_char(term, '=')
73-
if operator_pos == -1:
74-
quoted_name = True
75-
label_name = "__name__"
76-
else:
77-
value_start = _next_unquoted_char(term, '=')
78-
label_name, quoted_name = _unquote_unescape(term[:value_start])
79-
term = term[value_start + 1:]
71+
label_name, quoted_name = _unquote_unescape(name_term)
8072

8173
if not quoted_name and not _is_valid_legacy_metric_name(label_name):
8274
raise ValueError("unquoted UTF-8 metric name")
8375

8476
# Check for missing quotes
85-
term = term.strip()
86-
if not term or term[0] != '"':
77+
if not value_term or value_term[0] != '"':
8778
raise ValueError
8879

8980
# The first quote is guaranteed to be after the equal.
90-
# Find the last unescaped quote.
81+
# Make sure that the next unescaped quote is the last character.
9182
i = 1
92-
while i < len(term):
93-
i = term.index('"', i)
94-
if not _is_character_escaped(term[:i], i):
83+
while i < len(value_term):
84+
i = value_term.index('"', i)
85+
if not _is_character_escaped(value_term[:i], i):
9586
break
9687
i += 1
97-
9888
# The label value is between the first and last quote
9989
quote_end = i + 1
100-
if quote_end != len(term):
90+
if quote_end != len(value_term):
10191
raise ValueError("unexpected text after quote: " + labels_string)
102-
label_value, _ = _unquote_unescape(term[:quote_end])
92+
93+
label_value, _ = _unquote_unescape(value_term)
10394
if label_name == '__name__':
10495
_validate_metric_name(label_name)
10596
else:
@@ -112,11 +103,10 @@ def parse_labels(labels_string: str, openmetrics: bool = False) -> Dict[str, str
112103
raise ValueError("Invalid labels: " + labels_string)
113104

114105

115-
def _next_term(text: str, openmetrics: bool) -> Tuple[str, str]:
116-
"""Extract the next comma-separated label term from the text.
117-
118-
Returns the stripped term and the stripped remainder of the string,
119-
including the comma.
106+
def _next_term(text: str, openmetrics: bool) -> Tuple[str, str, str]:
107+
"""Extract the next comma-separated label term from the text. The results
108+
are stripped terms for the label name, label value, and then the remainder
109+
of the string including the final , or }.
120110
121111
Raises ValueError if the term is empty and we're in openmetrics mode.
122112
"""
@@ -125,18 +115,26 @@ def _next_term(text: str, openmetrics: bool) -> Tuple[str, str]:
125115
if text[0] == ',':
126116
text = text[1:]
127117
if not text:
128-
return "", ""
118+
return "", "", ""
129119
if text[0] == ',':
130120
raise ValueError("multiple commas")
131-
splitpos = _next_unquoted_char(text, ',}')
121+
122+
splitpos = _next_unquoted_char(text, '=,}')
123+
if splitpos >= 0 and text[splitpos] == "=":
124+
labelname = text[:splitpos]
125+
text = text[splitpos + 1:]
126+
splitpos = _next_unquoted_char(text, ',}')
127+
else:
128+
labelname = "__name__"
129+
132130
if splitpos == -1:
133131
splitpos = len(text)
134132
term = text[:splitpos]
135133
if not term and openmetrics:
136134
raise ValueError("empty term:", term)
137135

138-
sublabels = text[splitpos:]
139-
return term.strip(), sublabels.strip()
136+
rest = text[splitpos:]
137+
return labelname, term.strip(), rest.strip()
140138

141139

142140
def _next_unquoted_char(text: str, chs: Optional[str], startidx: int = 0) -> int:

0 commit comments

Comments
 (0)