blob: 9931759cbdf01ac2fb424ee2c65e4c4a92051099 [file] [log] [blame]
Wei-Yu Chenad55cb82022-02-15 20:07:01 +08001# SPDX-FileCopyrightText: 2020 The Magma Authors.
2# SPDX-FileCopyrightText: 2022 Open Networking Foundation <support@opennetworking.org>
3#
4# SPDX-License-Identifier: BSD-3-Clause
Wei-Yu Chen49950b92021-11-08 19:19:18 +08005
6import logging
7import time
8
9import metrics_pb2
10from orc8r.protos import metricsd_pb2
11from prometheus_client import REGISTRY
12
13
14def get_metrics(registry=REGISTRY, verbose=False):
15 """
16 Collects timeseries samples from prometheus metric collector registry
17 adds a common timestamp, and encodes them to protobuf
18
19 Arguments:
20 regsitry: a prometheus CollectorRegistry instance
21 verbose: whether to optimize for bandwidth and ignore metric name/help
22
23 Returns:
24 a prometheus MetricFamily protobuf stream
25 """
26 timestamp_ms = int(time.time() * 1000)
27 for metric_family in registry.collect():
28 if metric_family.type in ('counter', 'gauge'):
29 family_proto = encode_counter_gauge(metric_family, timestamp_ms)
30 elif metric_family.type == 'summary':
31 family_proto = encode_summary(metric_family, timestamp_ms)
32 elif metric_family.type == 'histogram':
33 family_proto = encode_histogram(metric_family, timestamp_ms)
34
35 if verbose:
36 family_proto.help = metric_family.documentation
37 family_proto.name = metric_family.name
38 else:
39 try:
40 family_proto.name = \
41 str(metricsd_pb2.MetricName.Value(metric_family.name))
42 except ValueError as e:
43 logging.debug(e) # If enum is not defined
44 family_proto.name = metric_family.name
45 yield family_proto
46
47
48def encode_counter_gauge(family, timestamp_ms):
49 """
50 Takes a Counter/Gauge family which is a collection of timeseries
51 samples that share a name (uniquely identified by labels) and yields
52 equivalent protobufs.
53
54 Each timeseries corresponds to a single sample tuple of the format:
55 (NAME, LABELS, VALUE)
56
57 Arguments:
58 family: a prometheus gauge metric family
59 timestamp_ms: the timestamp to attach to the samples
60 Raises:
61 ValueError if metric name is not defined in MetricNames protobuf
62 Returns:
63 A Counter or Gauge prometheus MetricFamily protobuf
64 """
65 family_proto = metrics_pb2.MetricFamily()
66 family_proto.type = \
67 metrics_pb2.MetricType.Value(family.type.upper())
68 for sample in family.samples:
69 metric_proto = metrics_pb2.Metric()
70 if family_proto.type == metrics_pb2.COUNTER:
71 metric_proto.counter.value = sample[2]
72 elif family_proto.type == metrics_pb2.GAUGE:
73 metric_proto.gauge.value = sample[2]
74 # Add meta-data to the timeseries
75 metric_proto.timestamp_ms = timestamp_ms
76 metric_proto.label.extend(_convert_labels_to_enums(sample[1].items()))
77 # Append metric sample to family
78 family_proto.metric.extend([metric_proto])
79 return family_proto
80
81
82def encode_summary(family, timestamp_ms):
83 """
84 Takes a Summary Metric family which is a collection of timeseries
85 samples that share a name (uniquely identified by labels) and yields
86 equivalent protobufs.
87
88 Each summary timeseries consists of sample tuples for the count, sum,
89 and quantiles in the format (NAME,LABELS,VALUE). The NAME is suffixed
90 with either _count, _sum to indicate count and sum respectively.
91 Quantile samples will be of the same NAME with quantile label.
92
93 Arguments:
94 family: a prometheus summary metric family
95 timestamp_ms: the timestamp to attach to the samples
96 Raises:
97 ValueError if metric name is not defined in MetricNames protobuf
98 Returns:
99 a Summary prometheus MetricFamily protobuf
100 """
101 family_proto = metrics_pb2.MetricFamily()
102 family_proto.type = metrics_pb2.SUMMARY
103 metric_protos = {}
104 # Build a map of each of the summary timeseries from the samples
105 for sample in family.samples:
106 quantile = sample[1].pop('quantile', None) # Remove from label set
107 # Each time series identified by label set excluding the quantile
108 metric_proto = \
109 metric_protos.setdefault(
110 frozenset(sample[1].items()),
111 metrics_pb2.Metric(),
112 )
113 if sample[0].endswith('_count'):
114 metric_proto.summary.sample_count = int(sample[2])
115 elif sample[0].endswith('_sum'):
116 metric_proto.summary.sample_sum = sample[2]
117 elif quantile:
118 quantile = metric_proto.summary.quantile.add()
119 quantile.value = sample[2]
120 quantile.quantile = _goStringToFloat(quantile)
121 # Go back and add meta-data to the timeseries
122 for labels, metric_proto in metric_protos.items():
123 metric_proto.timestamp_ms = timestamp_ms
124 metric_proto.label.extend(_convert_labels_to_enums(labels))
125 # Add it to the family
126 family_proto.metric.extend([metric_proto])
127 return family_proto
128
129
130def encode_histogram(family, timestamp_ms):
131 """
132 Takes a Histogram Metric family which is a collection of timeseries
133 samples that share a name (uniquely identified by labels) and yields
134 equivalent protobufs.
135
136 Each summary timeseries consists of sample tuples for the count, sum,
137 and quantiles in the format (NAME,LABELS,VALUE). The NAME is suffixed
138 with either _count, _sum, _buckets to indicate count, sum and buckets
139 respectively. Bucket samples will also contain a le to indicate its
140 upper bound.
141
142 Arguments:
143 family: a prometheus histogram metric family
144 timestamp_ms: the timestamp to attach to the samples
145 Raises:
146 ValueError if metric name is not defined in MetricNames protobuf
147 Returns:
148 a Histogram prometheus MetricFamily protobuf
149 """
150 family_proto = metrics_pb2.MetricFamily()
151 family_proto.type = metrics_pb2.HISTOGRAM
152 metric_protos = {}
153 for sample in family.samples:
154 upper_bound = sample[1].pop('le', None) # Remove from label set
155 metric_proto = \
156 metric_protos.setdefault(
157 frozenset(sample[1].items()),
158 metrics_pb2.Metric(),
159 )
160 if sample[0].endswith('_count'):
161 metric_proto.histogram.sample_count = int(sample[2])
162 elif sample[0].endswith('_sum'):
163 metric_proto.histogram.sample_sum = sample[2]
164 elif sample[0].endswith('_bucket'):
165 quantile = metric_proto.histogram.bucket.add()
166 quantile.cumulative_count = int(sample[2])
167 quantile.upper_bound = _goStringToFloat(upper_bound)
168 # Go back and add meta-data to the timeseries
169 for labels, metric_proto in metric_protos.items():
170 metric_proto.timestamp_ms = timestamp_ms
171 metric_proto.label.extend(_convert_labels_to_enums(labels))
172 # Add it to the family
173 family_proto.metric.extend([metric_proto])
174 return family_proto
175
176
177def _goStringToFloat(s):
178 if s == '+Inf':
179 return float("inf")
180 elif s == '-Inf':
181 return float("-inf")
182 elif s == 'NaN':
183 return float('nan')
184 else:
185 return float(s)
186
187
188def _convert_labels_to_enums(labels):
189 """
190 Try to convert both the label names and label values to enum values.
191 Defaults to the given name and value if it fails to convert.
192 Arguments:
193 labels: an array of label pairs that may contain enum names
194 Returns:
195 an array of label pairs with enum names converted to enum values
196 """
197 new_labels = []
198 for name, value in labels:
199 try:
200 name = str(metricsd_pb2.MetricLabelName.Value(name))
201 except ValueError as e:
202 logging.debug(e)
203 new_labels.append(metrics_pb2.LabelPair(name=name, value=value))
204 return new_labels