Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit a2f7685

Browse files
authored
Respect default labels in StackdriverStatsExporter (#622)
1 parent 4f143ba commit a2f7685

File tree

2 files changed

+250
-102
lines changed

2 files changed

+250
-102
lines changed

contrib/opencensus-ext-stackdriver/opencensus/ext/stackdriver/stats_exporter/__init__.py

Lines changed: 72 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
from opencensus.common import utils
2828
from opencensus.common.monitored_resource import monitored_resource
2929
from opencensus.common.version import __version__
30+
from opencensus.metrics import label_key
31+
from opencensus.metrics import label_value
3032
from opencensus.metrics import transport
3133
from opencensus.metrics.export import metric as metric_module
3234
from opencensus.metrics.export import metric_descriptor
@@ -65,70 +67,67 @@
6567

6668

6769
class Options(object):
68-
""" Options contains options for configuring the exporter.
70+
"""Exporter configuration options.
71+
72+
`resource` is an optional field that represents the Stackdriver monitored
73+
resource type. If unset, this defaults to a `MonitoredResource` with type
74+
"global" and no resource labels.
75+
76+
`default_monitoring_labels` are labels added to every metric created by
77+
this exporter. If unset, this defaults to a single label with key
78+
"opencensus_task" and value "py-<pid>@<hostname>". This default ensures
79+
that the set of labels together with the default resource (global) are
80+
unique to this process, as required by stackdriver.
81+
82+
If you set `default_monitoring_labels`, make sure that the `resource`
83+
field together with these labels is unique to the current process. This is
84+
to ensure that there is only a single writer to each time series in
85+
Stackdriver.
86+
87+
Set `default_monitoring_labels` to `{}` to avoid getting the default
88+
"opencensus_task" label. You should only do this if you know that
89+
`resource` uniquely identifies this process.
90+
91+
:type project_id: str
92+
:param project_id: The ID GCP project to export metrics to, fall back to
93+
default application credentials if unset.
94+
95+
:type resource: str
96+
:param resource: The stackdriver monitored resource type, defaults to
97+
global.
98+
99+
:type metric_prefix: str
100+
:param metric_prefix: Custom prefix for metric name and type.
101+
102+
:type default_monitoring_labels: dict(
103+
:class:`opencensus.metrics.label_key.LabelKey`,
104+
:class:`opencensus.metrics.label_value.LabelValue`)
105+
:param default_monitoring_labels: Default labels to be set on each exported
106+
metric.
69107
"""
108+
70109
def __init__(self,
71110
project_id="",
72111
resource="",
73112
metric_prefix="",
74113
default_monitoring_labels=None):
75-
self._project_id = project_id
76-
self._resource = resource
77-
self._metric_prefix = metric_prefix
78-
self._default_monitoring_labels = default_monitoring_labels
79-
80-
@property
81-
def project_id(self):
82-
""" project_id is the identifier of the Stackdriver
83-
project the user is uploading the stats data to.
84-
If not set, this will default to
85-
your "Application Default Credentials".
86-
"""
87-
return self._project_id
88-
89-
@property
90-
def resource(self):
91-
""" Resource is an optional field that represents the Stackdriver
92-
MonitoredResource type, a resource that can be used for monitoring.
93-
If no custom ResourceDescriptor is set, a default MonitoredResource
94-
with type global and no resource labels will be used.
95-
Optional.
96-
"""
97-
return self._resource
98-
99-
@property
100-
def metric_prefix(self):
101-
""" metric_prefix overrides the
102-
OpenCensus prefix of a stackdriver metric.
103-
Optional.
104-
"""
105-
return self._metric_prefix
106-
107-
@property
108-
def default_monitoring_labels(self):
109-
""" default_monitoring_labels are labels added to
110-
every metric created by this
111-
exporter in Stackdriver Monitoring.
112-
113-
If unset, this defaults to a single label
114-
with key "opencensus_task" and value "py-<pid>@<hostname>".
115-
This default ensures that the set of labels together with
116-
the default Resource (global) are unique to this
117-
process, as required by Stackdriver Monitoring.
118-
119-
If you set default_monitoring_labels,
120-
make sure that the Resource field
121-
together with these labels is unique to the
122-
current process. This is to ensure that
123-
there is only a single writer to
124-
each TimeSeries in Stackdriver.
125-
126-
Set this to Labels to avoid getting the
127-
default "opencensus_task" label.
128-
You should only do this if you know that
129-
the Resource you set uniquely identifies this Python process.
130-
"""
131-
return self._default_monitoring_labels
114+
self.project_id = project_id
115+
self.resource = resource
116+
self.metric_prefix = metric_prefix
117+
118+
if default_monitoring_labels is None:
119+
self.default_monitoring_labels = {
120+
label_key.LabelKey(OPENCENSUS_TASK,
121+
OPENCENSUS_TASK_DESCRIPTION):
122+
label_value.LabelValue(get_task_value())
123+
}
124+
else:
125+
for key, val in default_monitoring_labels.items():
126+
if not isinstance(key, label_key.LabelKey):
127+
raise TypeError
128+
if not isinstance(val, label_value.LabelValue):
129+
raise TypeError
130+
self.default_monitoring_labels = default_monitoring_labels
132131

133132

134133
class StackdriverStatsExporter(object):
@@ -173,10 +172,10 @@ def create_time_series_list(self, metric):
173172
def _convert_series(self, metric, ts):
174173
"""Convert an OC timeseries to a SD series."""
175174
series = monitoring_v3.types.TimeSeries()
176-
series.metric.type = namespaced_view_name(
177-
metric.descriptor.name, self.options.metric_prefix)
175+
series.metric.type = self.get_metric_type(metric.descriptor)
178176

179-
series.metric.labels[OPENCENSUS_TASK] = get_task_value()
177+
for lk, lv in self.options.default_monitoring_labels.items():
178+
series.metric.labels[lk.key] = lv.value
180179

181180
for key, val in zip(metric.descriptor.label_keys, ts.label_values):
182181
if val.value is not None:
@@ -252,8 +251,8 @@ def _convert_point(self, metric, ts, point, sd_point):
252251
start_time.seconds = int(timestamp_start)
253252
start_time.nanos = int((timestamp_start - start_time.seconds) * 1e9)
254253

255-
def get_descriptor_type(self, oc_md):
256-
"""Get a SD descriptor type for an OC metric descriptor."""
254+
def get_metric_type(self, oc_md):
255+
"""Get a SD metric type for an OC metric descriptor."""
257256
return namespaced_view_name(oc_md.name, self.options.metric_prefix)
258257

259258
def get_metric_descriptor(self, oc_md):
@@ -268,13 +267,11 @@ def get_metric_descriptor(self, oc_md):
268267
else:
269268
display_name_prefix = DEFAULT_DISPLAY_NAME_PREFIX
270269

271-
default_labels = self.options.default_monitoring_labels
272-
if default_labels is None:
273-
default_labels = {}
274-
desc_labels = new_label_descriptors(default_labels, oc_md.label_keys)
270+
desc_labels = new_label_descriptors(
271+
self.options.default_monitoring_labels, oc_md.label_keys)
275272

276273
descriptor = monitoring_v3.types.MetricDescriptor(labels=desc_labels)
277-
metric_type = self.get_descriptor_type(oc_md)
274+
metric_type = self.get_metric_type(oc_md)
278275
descriptor.type = metric_type
279276
descriptor.metric_kind = metric_kind
280277
descriptor.value_type = value_type
@@ -289,16 +286,16 @@ def get_metric_descriptor(self, oc_md):
289286

290287
def register_metric_descriptor(self, oc_md):
291288
"""Register a metric descriptor with stackdriver."""
292-
descriptor_type = self.get_descriptor_type(oc_md)
289+
metric_type = self.get_metric_type(oc_md)
293290
with self._md_lock:
294-
if descriptor_type in self._md_cache:
295-
return self._md_cache[descriptor_type]
291+
if metric_type in self._md_cache:
292+
return self._md_cache[metric_type]
296293

297294
descriptor = self.get_metric_descriptor(oc_md)
298295
project_name = self.client.project_path(self.options.project_id)
299296
sd_md = self.client.create_metric_descriptor(project_name, descriptor)
300297
with self._md_lock:
301-
self._md_cache[descriptor_type] = sd_md
298+
self._md_cache[metric_type] = sd_md
302299
return sd_md
303300

304301

@@ -424,19 +421,12 @@ def new_label_descriptors(defaults, keys):
424421
that will be sent to Stackdriver Monitoring
425422
"""
426423
label_descriptors = []
427-
for key, lbl in defaults.items():
424+
for lk in itertools.chain.from_iterable((defaults.keys(), keys)):
428425
label = {}
429-
label["key"] = sanitize_label(key)
430-
label["description"] = lbl
426+
label["key"] = sanitize_label(lk.key)
427+
label["description"] = lk.description
431428
label_descriptors.append(label)
432429

433-
for label_key in keys:
434-
label = {}
435-
label["key"] = sanitize_label(label_key.key)
436-
label["description"] = sanitize_label(label_key.description)
437-
label_descriptors.append(label)
438-
label_descriptors.append({"key": OPENCENSUS_TASK,
439-
"description": OPENCENSUS_TASK_DESCRIPTION})
440430
return label_descriptors
441431

442432

0 commit comments

Comments
 (0)