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