Skip to content

Commit 7befeba

Browse files
committed
New parsing method for the remote metric server
I implemented three new generators which allows us to scrape the web federate endpoints of a Prometheus server a bit quicker than the utility provided by the prometheus client.
1 parent c0a0b43 commit 7befeba

1 file changed

Lines changed: 104 additions & 24 deletions

File tree

src/remote_metrics/server.py

Lines changed: 104 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env python
2+
import cProfile
23
import sys
34
import time
45
import os
@@ -16,14 +17,97 @@
1617
"""
1718

1819

20+
def family_iterator(text):
21+
"""
22+
A parsing iterator to scrape the web page of a federate endpoint of a Prometheus server.
23+
It iterates over sample_iterator.
24+
"""
25+
left_index = 0
26+
text_length = len(text)
27+
while left_index < text_length:
28+
if text[left_index] == "#": # Metric declaration
29+
left_index += 7
30+
right_index = left_index + 1
31+
while text[right_index] != " ": # Still in the metric name
32+
right_index += 1
33+
metric_name = text[left_index:right_index]
34+
left_index = right_index + 1
35+
while text[left_index] != "\n":
36+
left_index += 1
37+
38+
yield metric_name, sample_iterator(text, [left_index])
39+
else:
40+
left_index += 1
41+
42+
43+
def sample_iterator(text, ptr_index):
44+
"""
45+
A sample iterator used to parse the web page of a federate endpoint of a Prometheus server.
46+
It iterates over tuples composed by :
47+
- a dictionnary of labels and their values
48+
- the corresponding sample value (always float)
49+
"""
50+
label_dict = {}
51+
while text[ptr_index[0]] != "#":
52+
while text[ptr_index[0]] != "{": # Searching for the start of the labels
53+
ptr_index[0] += 1
54+
ptr_index[0] += 1 # We point to the first letter of the first label
55+
56+
for label, value in label_iterator(text, ptr_index):
57+
label_dict[label] = value
58+
59+
ptr_index[0] += 2 # We point at the value of the sample
60+
right_index = ptr_index[0] + 1
61+
while text[right_index] != " ":
62+
right_index += 1
63+
str_value = text[ptr_index[0]:right_index]
64+
if str_value == "NaN":
65+
value = 0
66+
else:
67+
value = float(str_value)
68+
69+
yield label_dict, value
70+
71+
ptr_index[0] = right_index
72+
while text[ptr_index[0]] != "\n":
73+
ptr_index[0] += 1
74+
ptr_index[0] += 1
75+
76+
77+
def label_iterator(text, ptr_index):
78+
while text[ptr_index[0]] != "}":
79+
if text[ptr_index[0]] == ",":
80+
# We ensure pointing to the start of the label name
81+
ptr_index[0] += 1
82+
83+
# We find the name
84+
right_index = ptr_index[0] + 1
85+
while text[right_index] != "=":
86+
right_index += 1
87+
name = text[ptr_index[0]:right_index]
88+
ptr_index[0] = right_index + 2
89+
90+
# We find the value
91+
right_index += 3
92+
while text[right_index] != '"':
93+
right_index += 1
94+
value = text[ptr_index[0]:right_index]
95+
96+
yield name, value
97+
98+
ptr_index[0] = right_index + 1
99+
100+
19101
class ScrapedGauge:
20102
def __init__(self, name, labels):
21103
self.name = name
22104
self.labels = tuple(labels[:])
23105
self._gauge = Gauge(name, "A identified metric found during scraping the /federate web page", list(self.labels))
24106

25107
def update(self, label_values, value, prefix):
108+
start_time = time.time()
26109
self._gauge.labels(* self._label_value_iterator(label_values, prefix)).set(value)
110+
return time.time() - start_time
27111

28112
def _label_value_iterator(self, label_values, prefix):
29113
for label in self.labels:
@@ -37,39 +121,36 @@ class MetricManager:
37121
def __init__(self, prefix):
38122
self.metrics = dict()
39123
self.prefix = prefix + ":"
124+
self.total_set_time = 0
40125

41-
def process(self, parsed_metric):
126+
def process(self, samples, name):
42127
"""
43128
Set the values for the given family metric
44129
and create it if it has never been encountered before
45130
"""
46-
metric = self.metrics.get(parsed_metric.name)
131+
metric = self.metrics.get(name)
47132
if metric:
48-
self._update(metric, parsed_metric)
133+
self._update(metric, samples)
49134
else:
50-
self._update(self._create(parsed_metric), parsed_metric)
135+
self._update(self._create(samples, name), samples)
51136

52-
def _update(self, metric, parsed_metric):
137+
def _update(self, metric, samples):
53138
"""
54139
Analyze the parsed labels and values and update the
55140
corresponding metric
56-
57-
:param metric: The Gauge object we identified
58-
:param parsed_metric: The structure which contains sample datas
59141
"""
60-
for sample in parsed_metric.samples:
61-
metric.update(sample[1], sample[2], self.prefix)
142+
for labels, value in samples:
143+
self.total_set_time += metric.update(labels, value, self.prefix)
62144

63-
def _create(self, parsed_metric):
145+
def _create(self, samples, name):
64146
"""
65147
Analyze the parsed metric to create a proper metric
66-
67-
:param parsed_metric: The structure which contains sample datas
68-
:return: The newly created metric
69148
"""
70-
labels = tuple(parsed_metric.samples[0][1].keys())
71-
new_metric = ScrapedGauge(parsed_metric.name, labels)
72-
self.metrics[parsed_metric.name] = new_metric
149+
150+
labels, value = next(samples)
151+
new_metric = ScrapedGauge(name, tuple(labels.keys()))
152+
self.metrics[name] = new_metric
153+
new_metric.update(labels, value, self.prefix)
73154
return new_metric
74155

75156

@@ -100,7 +181,7 @@ def start_server(init_wait_time, threshold, port):
100181
def main():
101182
# getting the env variables
102183
server_port = os.environ.get('INFRABOX_PORT', 8044)
103-
distant_prom_addr = os.environ.get('DISTANT_PROM_ADDR', "http://localhost:9090")
184+
distant_prom_addr = os.environ.get('DISTANT_PROM_ADDR', "http://local_host:9090")
104185
instance_prefix = os.environ.get('INSTANCE_LABEL_PREFIX', "distant")
105186

106187
url_to_scrape = distant_prom_addr + '''/federate?match[]={__name__=~"..*"}'''
@@ -124,12 +205,11 @@ def main():
124205
metric_manager = MetricManager(instance_prefix)
125206
while running:
126207
response = requests.get(url_to_scrape)
127-
families = parser.text_string_to_metric_families(response.content.decode("utf-8"))
128-
for family in families:
129-
if family.name not in skipped_metrics:
130-
metric_manager.process(family)
131-
132-
time.sleep(1.9)
208+
web_text = response.content.decode("utf-8")
209+
families = family_iterator(web_text)
210+
for name, samples in families:
211+
if name not in skipped_metrics:
212+
metric_manager.process(samples, name)
133213

134214

135215
if __name__ == '__main__':

0 commit comments

Comments
 (0)