Skip to content

Commit 24e4bc2

Browse files
authored
Adds gauge types for long- and double-valued metrics.
1 parent 5f93795 commit 24e4bc2

File tree

5 files changed

+547
-4
lines changed

5 files changed

+547
-4
lines changed

opencensus/metrics/export/gauge.py

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from collections import OrderedDict
16+
import six
17+
import threading
18+
19+
from opencensus.metrics.export import metric
20+
from opencensus.metrics.export import metric_descriptor
21+
from opencensus.metrics.export import point as point_module
22+
from opencensus.metrics.export import time_series
23+
from opencensus.metrics.export import value as value_module
24+
25+
26+
class GaugePointLong(object):
27+
"""An instantaneous measurement from a LongGauge.
28+
29+
A GaugePointLong represents the most recent measurement from a
30+
:class:`LongGauge` for a given set of label values.
31+
"""
32+
33+
def __init__(self):
34+
self.value = 0
35+
self._value_lock = threading.Lock()
36+
37+
def __repr__(self):
38+
return ("{}({})"
39+
.format(
40+
type(self).__name__,
41+
self.value
42+
))
43+
44+
def add(self, val):
45+
"""Add `val` to the current value.
46+
47+
:type val: int
48+
:param val: Value to add.
49+
"""
50+
if not isinstance(val, six.integer_types):
51+
raise ValueError("GaugePointLong only supports integer types")
52+
with self._value_lock:
53+
self.value += val
54+
55+
def set(self, val):
56+
"""Set the current value to `val`.
57+
58+
:type val: int
59+
:param val: Value to set.
60+
"""
61+
if not isinstance(val, six.integer_types):
62+
raise ValueError("GaugePointLong only supports integer types")
63+
with self._value_lock:
64+
self.value = val
65+
66+
67+
class GaugePointDouble(object):
68+
"""An instantaneous measurement from a DoubleGauge.
69+
70+
A GaugePointDouble represents the most recent measurement from a
71+
:class:`DoubleGauge` for a given set of label values.
72+
"""
73+
74+
def __init__(self):
75+
self.value = 0.0
76+
self._value_lock = threading.Lock()
77+
78+
def __repr__(self):
79+
return ("{}({})"
80+
.format(
81+
type(self).__name__,
82+
self.value
83+
))
84+
85+
def add(self, val):
86+
"""Add `val` to the current value.
87+
88+
:type val: float
89+
:param val: Value to add.
90+
"""
91+
with self._value_lock:
92+
self.value += val
93+
94+
def set(self, val):
95+
"""Set the current value to `val`.
96+
97+
:type val: float
98+
:param val: Value to set.
99+
"""
100+
with self._value_lock:
101+
self.value = float(val)
102+
103+
104+
class Gauge(object):
105+
"""A set of instantaneous measurements of the same type.
106+
107+
End users should use :class:`LongGauge` or :class:`DoubleGauge` instead of
108+
using this class directly.
109+
110+
The constructor arguments are used to create a
111+
:class:`opencensus.metrics.export.metric_descriptor.MetricDescriptor` for
112+
converted metrics. See that class for details.
113+
"""
114+
115+
def __init__(self, name, description, unit, label_keys):
116+
self._len_label_keys = len(label_keys)
117+
self.default_label_values = [None] * self._len_label_keys
118+
self.descriptor = metric_descriptor.MetricDescriptor(
119+
name, description, unit, self.descriptor_type, label_keys)
120+
self.points = OrderedDict()
121+
self._points_lock = threading.Lock()
122+
123+
def __repr__(self):
124+
return ('{}(descriptor.name="{}", points={})'
125+
.format(
126+
type(self).__name__,
127+
self.descriptor.name,
128+
self.points
129+
))
130+
131+
def _get_or_create_time_series(self, label_values):
132+
with self._points_lock:
133+
return self.points.setdefault(
134+
tuple(label_values), self.point_type())
135+
136+
def get_or_create_time_series(self, label_values):
137+
"""Get a mutable measurement for the given set of label values.
138+
139+
:type label_values: list(:class:`LabelValue`)
140+
:param label_values: The measurement's label values.
141+
142+
:rtype: :class:`GaugePointLong` or :class:`GaugePointDouble`
143+
:return: A mutable point that represents the last value of the
144+
measurement.
145+
"""
146+
if label_values is None:
147+
raise ValueError
148+
if any(lv is None for lv in label_values):
149+
raise ValueError
150+
if len(label_values) != self._len_label_keys:
151+
raise ValueError
152+
return self._get_or_create_time_series(label_values)
153+
154+
def get_or_create_default_time_series(self):
155+
"""Get the default measurement for this gauge.
156+
157+
Each gauge has a default point not associated with any specific label
158+
values. When this gauge is exported as a metric via `get_metric` the
159+
time series associated with this point will have null label values.
160+
161+
:rtype: :class:`GaugePointLong` or :class:`GaugePointDouble`
162+
:return: A mutable point that represents the last value of the
163+
measurement.
164+
"""
165+
return self._get_or_create_time_series(self.default_label_values)
166+
167+
def _remove_time_series(self, label_values):
168+
with self._points_lock:
169+
try:
170+
del self.points[tuple(label_values)]
171+
except KeyError:
172+
pass
173+
174+
def remove_time_series(self, label_values):
175+
"""Remove the time series for specific label values.
176+
177+
:type label_values: list(:class:`LabelValue`)
178+
:param label_values: Label values of the time series to remove.
179+
"""
180+
if label_values is None:
181+
raise ValueError
182+
if any(lv is None for lv in label_values):
183+
raise ValueError
184+
if len(label_values) != self._len_label_keys:
185+
raise ValueError
186+
self._remove_time_series(label_values)
187+
188+
def remove_default_time_series(self):
189+
"""Remove the default time series for this gauge."""
190+
self._remove_time_series(self.default_label_values)
191+
192+
def clear(self):
193+
"""Remove all points from this gauge."""
194+
with self._points_lock:
195+
self.points = OrderedDict()
196+
197+
def get_metric(self, timestamp):
198+
"""Get a metric including all current time series.
199+
200+
Get a :class:`opencensus.metrics.export.metric.Metric` with one
201+
:class:`opencensus.metrics.export.time_series.TimeSeries` for each
202+
set of label values with a recorded measurement. Each `TimeSeries`
203+
has a single point that represents the last recorded value.
204+
205+
:type timestamp: :class:`datetime.datetime`
206+
:param timestamp: Recording time to report, usually the current time.
207+
208+
:rtype: :class:`opencensus.metrics.export.metric.Metric` or None
209+
:return: A converted metric for all current measurements.
210+
"""
211+
if not self.points:
212+
return None
213+
214+
ts_list = []
215+
with self._points_lock:
216+
for lv, gp in self.points.items():
217+
point = point_module.Point(
218+
self.value_type(gp.value), timestamp)
219+
ts_list.append(time_series.TimeSeries(lv, [point], timestamp))
220+
return metric.Metric(self.descriptor, ts_list)
221+
222+
@property
223+
def descriptor_type(self): # pragma: NO COVER
224+
raise NotImplementedError
225+
226+
@property
227+
def point_type(self): # pragma: NO COVER
228+
raise NotImplementedError
229+
230+
@property
231+
def value_type(self): # pragma: NO COVER
232+
raise NotImplementedError
233+
234+
235+
class LongGauge(Gauge):
236+
"""Gauge for recording int-valued measurements."""
237+
descriptor_type = metric_descriptor.MetricDescriptorType.GAUGE_INT64
238+
point_type = GaugePointLong
239+
value_type = value_module.ValueLong
240+
241+
242+
class DoubleGauge(Gauge):
243+
"""Gauge for recording float-valued measurements."""
244+
descriptor_type = metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE
245+
point_type = GaugePointDouble
246+
value_type = value_module.ValueDouble

opencensus/metrics/export/metric.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,24 @@ def __init__(self, descriptor, time_series):
4343
self._check_type()
4444

4545
def __repr__(self):
46-
return ('{}(descriptor.name="{}")'
46+
# Get a condensed description of a list of time series like:
47+
# (k1="v11", k2="v21", [12]), (k1="v21", k2="v22", [34])
48+
lks = [lk.key for lk in self.descriptor.label_keys]
49+
labeled_pvs = []
50+
for ts in self.time_series:
51+
labels = ", ".join('{}="{}"'.format(lk, lv)
52+
for lk, lv in zip(lks, ts.label_values))
53+
pvs = [point.value.value for point in ts.points]
54+
labeled_pvs.append((labels, pvs))
55+
short_ts_repr = ", ".join("({}, {})".format(ll, pvs)
56+
for ll, pvs in labeled_pvs)
57+
58+
# E.g. 'Metric((k1="v11", k2="v21", [12]), descriptor.name="name")'
59+
return ('{}({}, descriptor.name="{}")'
4760
.format(
4861
type(self).__name__,
49-
self.descriptor.name
62+
short_ts_repr,
63+
self.descriptor.name,
5064
))
5165

5266
@property

opencensus/metrics/export/time_series.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ def __init__(self, label_values, points, start_timestamp):
4747
self._start_timestamp = start_timestamp
4848

4949
def __repr__(self):
50-
return ('{}(points={}, label_values={}, start_timestamp={})'
50+
return ('{}({}, label_values={}, start_timestamp={})'
5151
.format(
5252
type(self).__name__,
53-
self.points,
53+
[point.value.value for point in self.points],
5454
self.label_values,
5555
self.start_timestamp
5656
))

opencensus/metrics/export/value.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ class ValueLong(Value):
9797
def __init__(self, value):
9898
super(ValueLong, self).__init__(value)
9999

100+
def __repr__(self):
101+
return ("{}({})"
102+
.format(
103+
type(self).__name__,
104+
self.value,
105+
))
106+
100107

101108
class ValueSummary(Value):
102109
"""Represents a snapshot values calculated over an arbitrary time window.
@@ -108,6 +115,13 @@ class ValueSummary(Value):
108115
def __init__(self, value):
109116
super(ValueSummary, self).__init__(value)
110117

118+
def __repr__(self):
119+
return ("{}({})"
120+
.format(
121+
type(self).__name__,
122+
self.value,
123+
))
124+
111125

112126
class Exemplar(object):
113127
"""An example point to annotate a given value in a bucket.

0 commit comments

Comments
 (0)