Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Unreleased

## 0.4.0
Released 2019-04-11

- Allow for metrics with empty label keys and values
([#611](https://github.com/census-instrumentation/opencensus-python/pull/611))
([#614](https://github.com/census-instrumentation/opencensus-python/pull/614))

## 0.4.0
Released 2019-04-08

Expand Down
5 changes: 5 additions & 0 deletions contrib/opencensus-ext-stackdriver/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

## 0.2.0
Released 2019-04-11
- Don't require exporter options, fall back to default GCP auth
([#610](https://github.com/census-instrumentation/opencensus-python/pull/610))

## 0.2.0
Released 2019-04-08

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from google.api_core.gapic_v1 import client_info
from google.cloud import monitoring_v3
import google.auth

from opencensus.common import utils
from opencensus.common.monitored_resource import monitored_resource
Expand Down Expand Up @@ -365,12 +366,16 @@ def get_user_agent_slug():
return "opencensus-python/{}".format(__version__)


def new_stats_exporter(options, interval=None):
def new_stats_exporter(options=None, interval=None):
"""Get a stats exporter and running transport thread.

Create a new `StackdriverStatsExporter` with the given options and start
periodically exporting stats to stackdriver in the background.

Fall back to default auth if `options` is null. This will raise
`google.auth.exceptions.DefaultCredentialsError` if default credentials
aren't configured.

See `opencensus.metrics.transport.get_exporter_thread` for details on the
transport thread.

Expand All @@ -380,9 +385,12 @@ def new_stats_exporter(options, interval=None):
:type interval: int or float
:param interval: Seconds between export calls.

:rtype: :class:`StackdriverStatsExporter` and :class:`PeriodicTask`
:return: A tuple of the exporter and transport thread.
:rtype: :class:`StackdriverStatsExporter`
:return: The newly-created exporter.
"""
if options is None:
_, project_id = google.auth.default()
options = Options(project_id=project_id)
if str(options.project_id).strip() == "":
raise ValueError(ERROR_BLANK_PROJECT_ID)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import unittest

from google.cloud import monitoring_v3
import google.auth

from opencensus.common import utils
from opencensus.common.version import __version__
Expand Down Expand Up @@ -115,6 +116,32 @@ def test_constructor_param(self):
default_monitoring_labels=default_labels))
self.assertEqual(exporter.options.project_id, project_id)

def test_null_options(self):
# Check that we don't suppress auth errors
auth_error = google.auth.exceptions.DefaultCredentialsError
mock_auth_error = mock.Mock()
mock_auth_error.side_effect = auth_error
with mock.patch('opencensus.ext.stackdriver.stats_exporter'
'.google.auth.default', mock_auth_error):
with self.assertRaises(auth_error):
stackdriver.new_stats_exporter()

# Check that we get the default credentials' project ID
mock_auth_ok = mock.Mock()
mock_auth_ok.return_value = (None, 123)
with mock.patch('opencensus.ext.stackdriver.stats_exporter'
'.google.auth.default', mock_auth_ok):
sdse = stackdriver.new_stats_exporter()
self.assertEqual(sdse.options.project_id, 123)

# Check that we raise if auth works but the project is empty
mock_auth_no_project = mock.Mock()
mock_auth_no_project.return_value = (None, '')
with mock.patch('opencensus.ext.stackdriver.stats_exporter'
'.google.auth.default', mock_auth_no_project):
with self.assertRaises(ValueError):
stackdriver.new_stats_exporter()

def test_blank_project(self):
self.assertRaises(ValueError, stackdriver.new_stats_exporter,
stackdriver.Options(project_id=""))
Expand Down
2 changes: 1 addition & 1 deletion contrib/opencensus-ext-stackdriver/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = '0.2.0'
__version__ = '0.2.1'
2 changes: 1 addition & 1 deletion opencensus/common/version/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = '0.4.0'
__version__ = '0.4.1'
4 changes: 2 additions & 2 deletions opencensus/metrics/export/metric_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ def __init__(self, name, description, unit, type_, label_keys):
if type_ not in MetricDescriptorType:
raise ValueError("Invalid type")

if not label_keys:
raise ValueError("label_keys must not be empty or null")
if label_keys is None:
raise ValueError("label_keys must not be None")

if any(key is None for key in label_keys):
raise ValueError("label_keys must not contain null keys")
Expand Down
4 changes: 2 additions & 2 deletions opencensus/metrics/export/time_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class TimeSeries(object):
""" # noqa

def __init__(self, label_values, points, start_timestamp):
if not label_values:
raise ValueError("label_values must not be null or empty")
if label_values is None:
raise ValueError("label_values must not be None")
if not points:
raise ValueError("points must not be null or empty")
self._label_values = label_values
Expand Down
8 changes: 4 additions & 4 deletions opencensus/trace/propagation/b3_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ def from_headers(self, headers):
sampled = headers.get(_SAMPLED_KEY)

if sampled is not None:
if len(sampled) != 1:
return SpanContext(from_header=False)

sampled = sampled in ('1', 'd')
# The specification encodes an enabled tracing decision as "1".
# In the wild pre-standard implementations might still send "true".
# "d" is set in the single header case when debugging is enabled.
sampled = sampled.lower() in ('1', 'd', 'true')
else:
# If there's no incoming sampling decision, it was deferred to us.
# Even though we set it to False here, we might still sample
Expand Down
5 changes: 5 additions & 0 deletions tests/unit/metrics/export/test_metric_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ def test_null_label_keys(self):
NAME, DESCRIPTION, UNIT,
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE, None)

def test_empty_label_keys(self):
metric_descriptor.MetricDescriptor(
NAME, DESCRIPTION, UNIT,
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE, [])

def test_null_label_key_values(self):
with self.assertRaises(ValueError):
metric_descriptor.MetricDescriptor(
Expand Down
2 changes: 0 additions & 2 deletions tests/unit/metrics/export/test_time_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ def test_init_invalid(self):
time_series.TimeSeries(LABEL_VALUES, POINTS, None)
with self.assertRaises(ValueError):
time_series.TimeSeries(None, POINTS, START_TIMESTAMP)
with self.assertRaises(ValueError):
time_series.TimeSeries([], POINTS, START_TIMESTAMP)
with self.assertRaises(ValueError):
time_series.TimeSeries(LABEL_VALUES, None, START_TIMESTAMP)
with self.assertRaises(ValueError):
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/stats/test_metric_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,36 @@ def test_view_data_to_metric(self):
]
for args in args_list:
self.do_test_view_data_to_metric(*args)

def test_convert_view_without_labels(self):
mock_measure = mock.Mock(spec=measure.MeasureFloat)
mock_aggregation = mock.Mock(spec=aggregation.DistributionAggregation)
mock_aggregation.aggregation_type = aggregation.Type.DISTRIBUTION

vd = mock.Mock(spec=view_data.ViewData)
vd.view = view.View(
name=mock.Mock(),
description=mock.Mock(),
columns=[],
measure=mock_measure,
aggregation=mock_aggregation)
vd.start_time = '2019-04-11T22:33:44.555555Z'

mock_point = mock.Mock(spec=point.Point)
mock_point.value = mock.Mock(spec=value.ValueDistribution)

mock_agg = mock.Mock(spec=aggregation_data.SumAggregationDataFloat)
mock_agg.to_point.return_value = mock_point

vd.tag_value_aggregation_data_map = {tuple(): mock_agg}

current_time = '2019-04-11T22:33:55.666666Z'
metric = metric_utils.view_data_to_metric(vd, current_time)

self.assertEqual(metric.descriptor.label_keys, [])
self.assertEqual(len(metric.time_series), 1)
[ts] = metric.time_series
self.assertEqual(ts.label_values, [])
self.assertEqual(len(ts.points), 1)
[pt] = ts.points
self.assertEqual(pt, mock_point)
49 changes: 35 additions & 14 deletions tests/unit/trace/propagation/test_b3_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,44 @@ def test_from_headers_no_headers(self):
def test_from_headers_keys_exist(self):
test_trace_id = '6e0c63257de34c92bf9efcd03927272e'
test_span_id = '00f067aa0ba902b7'
test_sampled = '1'

headers = {
b3_format._TRACE_ID_KEY: test_trace_id,
b3_format._SPAN_ID_KEY: test_span_id,
b3_format._SAMPLED_KEY: test_sampled,
}
for test_sampled in ['1', 'True', 'true', 'd']:
headers = {
b3_format._TRACE_ID_KEY: test_trace_id,
b3_format._SPAN_ID_KEY: test_span_id,
b3_format._SAMPLED_KEY: test_sampled,
}

propagator = b3_format.B3FormatPropagator()
span_context = propagator.from_headers(headers)
propagator = b3_format.B3FormatPropagator()
span_context = propagator.from_headers(headers)

self.assertEqual(span_context.trace_id, test_trace_id)
self.assertEqual(span_context.span_id, test_span_id)
self.assertEqual(
span_context.trace_options.enabled,
bool(test_sampled)
)
self.assertEqual(span_context.trace_id, test_trace_id)
self.assertEqual(span_context.span_id, test_span_id)
self.assertEqual(
span_context.trace_options.enabled,
True
)

def test_from_headers_keys_exist_disabled_sampling(self):
test_trace_id = '6e0c63257de34c92bf9efcd03927272e'
test_span_id = '00f067aa0ba902b7'

for test_sampled in ['0', 'False', 'false', None]:
headers = {
b3_format._TRACE_ID_KEY: test_trace_id,
b3_format._SPAN_ID_KEY: test_span_id,
b3_format._SAMPLED_KEY: test_sampled,
}

propagator = b3_format.B3FormatPropagator()
span_context = propagator.from_headers(headers)

self.assertEqual(span_context.trace_id, test_trace_id)
self.assertEqual(span_context.span_id, test_span_id)
self.assertEqual(
span_context.trace_options.enabled,
False
)

def test_from_headers_keys_not_exist(self):
propagator = b3_format.B3FormatPropagator()
Expand Down