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
24 changes: 2 additions & 22 deletions opencensus/metrics/export/metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,6 @@
# limitations under the License.

from opencensus.metrics.export import metric_descriptor
from opencensus.metrics.export import value


DESCRIPTOR_VALUE = {
metric_descriptor.MetricDescriptorType.GAUGE_INT64:
value.ValueLong,
metric_descriptor.MetricDescriptorType.CUMULATIVE_INT64:
value.ValueLong,
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE:
value.ValueDouble,
metric_descriptor.MetricDescriptorType.CUMULATIVE_DOUBLE:
value.ValueDouble,
metric_descriptor.MetricDescriptorType.GAUGE_DISTRIBUTION:
value.ValueDistribution,
metric_descriptor.MetricDescriptorType.CUMULATIVE_DISTRIBUTION:
value.ValueDistribution,
metric_descriptor.MetricDescriptorType.SUMMARY:
value.ValueSummary,
}


class Metric(object):
Expand Down Expand Up @@ -71,9 +52,8 @@ def descriptor(self):

def _check_type(self):
"""Check that point value types match the descriptor type."""
check_type = DESCRIPTOR_VALUE.get(self.descriptor.type)
if check_type is None:
raise ValueError("Unknown metric descriptor type")
check_type = metric_descriptor.MetricDescriptorType.to_type_class(
self.descriptor.type)
for ts in self.time_series:
if not ts.check_points_type(check_type):
raise ValueError("Invalid point value type")
Expand Down
28 changes: 14 additions & 14 deletions opencensus/metrics/export/metric_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

import six

from opencensus.metrics.export.value import ValueDistribution
from opencensus.metrics.export.value import ValueSummary
from opencensus.metrics.export import value


class _MetricDescriptorTypeMeta(type):
Expand Down Expand Up @@ -81,19 +80,20 @@ class MetricDescriptorType(object):
# is not recommended, since it cannot be aggregated.
SUMMARY = 7

_type_map = {
GAUGE_INT64: value.ValueLong,
GAUGE_DOUBLE: value.ValueDouble,
GAUGE_DISTRIBUTION: value.ValueDistribution,
CUMULATIVE_INT64: value.ValueLong,
CUMULATIVE_DOUBLE: value.ValueDouble,
CUMULATIVE_DISTRIBUTION: value.ValueDistribution,
SUMMARY: value.ValueSummary
}

@classmethod
def to_type_class(cls, metric_descriptor_type):
type_map = {
cls.GAUGE_INT64: int,
cls.GAUGE_DOUBLE: float,
cls.GAUGE_DISTRIBUTION: ValueDistribution,
cls.CUMULATIVE_INT64: int,
cls.CUMULATIVE_DOUBLE: float,
cls.CUMULATIVE_DISTRIBUTION: ValueDistribution,
cls.SUMMARY: ValueSummary
}
try:
return type_map[metric_descriptor_type]
return cls._type_map[metric_descriptor_type]
except KeyError:
raise ValueError("Unknown MetricDescriptorType value")

Expand All @@ -119,8 +119,8 @@ class MetricDescriptor(object):
format described by http://unitsofmeasure.org/ucum.html.

:type type_: int
:param unit: The unit in which the metric value is reported. The
MetricDescriptorType class enumerates valid options.
:param type_: The type of metric. MetricDescriptorType enumerates the valid
options.

:type label_keys: list(:class: '~opencensus.metrics.label_key.LabelKey')
:param label_keys: The label keys associated with the metric descriptor.
Expand Down
20 changes: 11 additions & 9 deletions opencensus/metrics/export/time_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from opencensus.metrics.export import metric_descriptor


class TimeSeries(object):
"""Time series data for a given metric and time interval.
Expand Down Expand Up @@ -60,15 +58,19 @@ def label_values(self):
def points(self):
return self._points

def check_points_type(self, type_):
"""Check that each point's value is an instance `type_`.
def check_points_type(self, type_class):
"""Check that each point's value is an instance of `type_class`.

`type_class` should typically be a Value type, i.e. one that extends
:class: `opencensus.metrics.export.value.Value`.

:type type_class: type
:param type_class: Type to check against.

:type type_: type
:param type_: Type to check against.
:rtype: bool
:return: Whether all points are instances of `type_class`.
"""
type_class = (
metric_descriptor.MetricDescriptorType.to_type_class(type_))
for point in self.points:
if not isinstance(point.value.value, type_class):
if not isinstance(point.value, type_class):
return False
return True
2 changes: 1 addition & 1 deletion opencensus/metrics/export/value.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class Bucket(object):
distribution does not have a histogram.
"""

def __init__(self, count, exemplar):
def __init__(self, count, exemplar=None):
self._count = count
self._exemplar = exemplar

Expand Down
111 changes: 101 additions & 10 deletions opencensus/stats/aggregation_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
import logging

from opencensus.metrics.export import point
from opencensus.metrics.export import value
from opencensus.stats import bucket_boundaries


Expand All @@ -36,6 +39,18 @@ def aggregation_data(self):
"""The current aggregation data"""
return self._aggregation_data

def to_point(self, timestamp):
"""Get a Point conversion of this aggregation.

:type timestamp: :class: `datetime.datetime`
:param timestamp: The time to report the point as having been recorded.

:rtype: :class: `opencensus.metrics.export.point.Point`
:return: a Point with with this aggregation's value and appropriate
value type.
"""
raise NotImplementedError # pragma: NO COVER


class SumAggregationDataFloat(BaseAggregationData):
"""Sum Aggregation Data is the aggregated data for the Sum aggregation
Expand All @@ -60,6 +75,18 @@ def sum_data(self):
"""The current sum data"""
return self._sum_data

def to_point(self, timestamp):
"""Get a Point conversion of this aggregation.

:type timestamp: :class: `datetime.datetime`
:param timestamp: The time to report the point as having been recorded.

:rtype: :class: `opencensus.metrics.export.point.Point`
:return: a :class: `opencensus.metrics.export.value.ValueDouble`-valued
Point with value equal to `sum_data`.
"""
return point.Point(value.ValueDouble(self.sum_data), timestamp)


class CountAggregationData(BaseAggregationData):
"""Count Aggregation Data is the count value of aggregated data
Expand All @@ -83,6 +110,18 @@ def count_data(self):
"""The current count data"""
return self._count_data

def to_point(self, timestamp):
"""Get a Point conversion of this aggregation.

:type timestamp: :class: `datetime.datetime`
:param timestamp: The time to report the point as having been recorded.

:rtype: :class: `opencensus.metrics.export.point.Point`
:return: a :class: `opencensus.metrics.export.value.ValueLong`-valued
Point with value equal to `count_data`.
"""
return point.Point(value.ValueLong(self.count_data), timestamp)


class DistributionAggregationData(BaseAggregationData):
"""Distribution Aggregation Data refers to the distribution stats of
Expand Down Expand Up @@ -123,34 +162,37 @@ def __init__(self,
counts_per_bucket=None,
bounds=None,
exemplars=None):
if bounds is None and exemplars is not None:
raise ValueError
if exemplars is not None and len(exemplars) != len(bounds) + 1:
raise ValueError

super(DistributionAggregationData, self).__init__(mean_data)
self._mean_data = mean_data
self._count_data = count_data
self._min = min_
self._max = max_
self._sum_of_sqd_deviations = sum_of_sqd_deviations

if bounds is None:
bounds = []
self._exemplars = None
else:
assert bounds == list(sorted(set(bounds)))
assert all(bb > 0 for bb in bounds)
if exemplars is None:
self._exemplars = {ii: None for ii in range(len(bounds) + 1)}
else:
self._exemplars = {ii: ex for ii, ex in enumerate(exemplars)}
self._bounds = (bucket_boundaries.BucketBoundaries(boundaries=bounds)
.boundaries)

if counts_per_bucket is None:
counts_per_bucket = [0 for ii in range(len(bounds) + 1)]
else:
assert all(cc >= 0 for cc in counts_per_bucket)
assert len(counts_per_bucket) == len(bounds) + 1

self._counts_per_bucket = counts_per_bucket
self._bounds = bucket_boundaries.BucketBoundaries(
boundaries=bounds).boundaries
bucket = 0
for _ in self.bounds:
bucket = bucket + 1

# If there is no histogram, do not record an exemplar
self._exemplars = \
{bucket: exemplars} if len(self._bounds) > 0 else None
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this is a typo and this line should have been indented. Score one for semantic whitespace detractors.


@property
def mean_data(self):
Expand Down Expand Up @@ -240,6 +282,43 @@ def increment_bucket_count(self, value):
self._counts_per_bucket[last_bucket_index] += 1
return last_bucket_index

def to_point(self, timestamp):
"""Get a Point conversion of this aggregation.

This method creates a :class: `opencensus.metrics.export.point.Point`
with a :class: `opencensus.metrics.export.value.ValueDistribution`
value, and creates buckets and exemplars for that distribution from the
appropriate classes in the `metrics` package.

:type timestamp: :class: `datetime.datetime`
:param timestamp: The time to report the point as having been recorded.

:rtype: :class: `opencensus.metrics.export.point.Point`
:return: a :class: `opencensus.metrics.export.value.ValueDistribution`
-valued Point.
"""
buckets = [None] * len(self.counts_per_bucket)
for ii, count in enumerate(self.counts_per_bucket):
stat_ex = self.exemplars.get(ii, None)
if stat_ex is not None:
metric_ex = value.Exemplar(stat_ex.value, stat_ex.timestamp,
copy.copy(stat_ex.attachments))
buckets[ii] = value.Bucket(count, metric_ex)
else:
buckets[ii] = value.Bucket(count)

bucket_options = value.BucketOptions(value.Explicit(self.bounds))
return point.Point(
value.ValueDistribution(
count=self.count_data,
sum_=self.sum,
sum_of_squared_deviation=self.sum_of_sqd_deviations,
bucket_options=bucket_options,
buckets=buckets
),
timestamp
)


class LastValueAggregationData(BaseAggregationData):
"""
Expand All @@ -265,6 +344,18 @@ def value(self):
"""The current value recorded"""
return self._value

def to_point(self, timestamp):
"""Get a Point conversion of this aggregation.

:type timestamp: :class: `datetime.datetime`
:param timestamp: The time to report the point as having been recorded.

:rtype: :class: `opencensus.metrics.export.point.Point`
:return: a :class: `opencensus.metrics.export.value.ValueDouble`-valued
Point.
"""
return point.Point(value.ValueDouble(self.value), timestamp)


class Exemplar(object):
""" Exemplar represents an example point that may be used to annotate
Expand Down
49 changes: 27 additions & 22 deletions tests/unit/metrics/export/test_metric_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,53 @@

import unittest

from opencensus.metrics.export.metric_descriptor import MetricDescriptor
from opencensus.metrics.export.metric_descriptor import MetricDescriptorType
from opencensus.metrics.label_key import LabelKey
from opencensus.metrics import label_key
from opencensus.metrics.export import metric_descriptor
from opencensus.metrics.export import value

NAME = 'metric'
DESCRIPTION = 'Metric description'
UNIT = '0.738.[ft_i].[lbf_av]/s'
LABEL_KEY1 = LabelKey('key1', 'key description one')
LABEL_KEY2 = LabelKey('值', '测试用键')
LABEL_KEY1 = label_key.LabelKey('key1', 'key description one')
LABEL_KEY2 = label_key.LabelKey('值', '测试用键')
LABEL_KEYS = (LABEL_KEY1, LABEL_KEY2)


class TestMetricDescriptor(unittest.TestCase):
def test_init(self):
metric_descriptor = MetricDescriptor(NAME, DESCRIPTION, UNIT,
MetricDescriptorType.GAUGE_DOUBLE,
(LABEL_KEY1, LABEL_KEY2))
md = metric_descriptor.MetricDescriptor(
NAME, DESCRIPTION, UNIT,
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE,
(LABEL_KEY1, LABEL_KEY2))

self.assertEqual(metric_descriptor.name, NAME)
self.assertEqual(metric_descriptor.description, DESCRIPTION)
self.assertEqual(metric_descriptor.unit, UNIT)
self.assertEqual(metric_descriptor.type,
MetricDescriptorType.GAUGE_DOUBLE)
self.assertEqual(metric_descriptor.label_keys, LABEL_KEYS)
self.assertEqual(md.name, NAME)
self.assertEqual(md.description, DESCRIPTION)
self.assertEqual(md.unit, UNIT)
self.assertEqual(md.type,
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE)
self.assertEqual(md.label_keys, LABEL_KEYS)

def test_bogus_type(self):
with self.assertRaises(ValueError):
MetricDescriptor(NAME, DESCRIPTION, UNIT, 0, (LABEL_KEY1, ))
metric_descriptor.MetricDescriptor(NAME, DESCRIPTION, UNIT, 0,
(LABEL_KEY1, ))

def test_null_label_keys(self):
with self.assertRaises(ValueError):
MetricDescriptor(NAME, DESCRIPTION, UNIT,
MetricDescriptorType.GAUGE_DOUBLE, None)
metric_descriptor.MetricDescriptor(
NAME, DESCRIPTION, UNIT,
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE, None)

def test_null_label_key_values(self):
with self.assertRaises(ValueError):
MetricDescriptor(NAME, DESCRIPTION, UNIT,
MetricDescriptorType.GAUGE_DOUBLE, (None, ))
metric_descriptor.MetricDescriptor(
NAME, DESCRIPTION, UNIT,
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE, (None, ))

def test_to_type_class(self):
self.assertEqual(
MetricDescriptorType.to_type_class(
MetricDescriptorType.GAUGE_INT64), int)
metric_descriptor.MetricDescriptorType.to_type_class(
metric_descriptor.MetricDescriptorType.GAUGE_INT64),
value.ValueLong)
with self.assertRaises(ValueError):
MetricDescriptorType.to_type_class(10)
metric_descriptor.MetricDescriptorType.to_type_class(10)
Loading