diff --git a/opencensus/stats/metric_utils.py b/opencensus/stats/metric_utils.py new file mode 100644 index 000000000..69f4eb349 --- /dev/null +++ b/opencensus/stats/metric_utils.py @@ -0,0 +1,83 @@ +# Copyright 2018, OpenCensus Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Utilities to convert stats data models to metrics data models. +""" + +from opencensus.metrics import label_key +from opencensus.metrics.export import metric_descriptor +from opencensus.stats import aggregation as aggregation_module +from opencensus.stats import measure as measure_module + +# To check that an aggregation's reported type matches its class +AGGREGATION_TYPE_MAP = { + aggregation_module.Type.SUM: + aggregation_module.SumAggregation, + aggregation_module.Type.COUNT: + aggregation_module.CountAggregation, + aggregation_module.Type.DISTRIBUTION: + aggregation_module.DistributionAggregation, + aggregation_module.Type.LASTVALUE: + aggregation_module.LastValueAggregation, +} + + +def get_metric_type(measure, aggregation): + """Get the corresponding metric type for the given stats type. + + :type measure: (:class: '~opencensus.stats.measure.BaseMeasure') + :param measure: the measure for which to find a metric type + + :type aggregation: (:class: + '~opencensus.stats.aggregation.BaseAggregation') + :param aggregation: the aggregation for which to find a metric type + """ + if aggregation.aggregation_type == aggregation_module.Type.NONE: + raise ValueError("aggregation type must not be NONE") + assert isinstance(aggregation, + AGGREGATION_TYPE_MAP[aggregation.aggregation_type]) + + if aggregation.aggregation_type == aggregation_module.Type.SUM: + if isinstance(measure, measure_module.MeasureInt): + return metric_descriptor.MetricDescriptorType.CUMULATIVE_INT64 + elif isinstance(measure, measure_module.MeasureFloat): + return metric_descriptor.MetricDescriptorType.CUMULATIVE_DOUBLE + else: + raise ValueError + elif aggregation.aggregation_type == aggregation_module.Type.COUNT: + return metric_descriptor.MetricDescriptorType.CUMULATIVE_INT64 + elif aggregation.aggregation_type == aggregation_module.Type.DISTRIBUTION: + return metric_descriptor.MetricDescriptorType.CUMULATIVE_DISTRIBUTION + elif aggregation.aggregation_type == aggregation_module.Type.LASTVALUE: + if isinstance(measure, measure_module.MeasureInt): + return metric_descriptor.MetricDescriptorType.GAUGE_INT64 + elif isinstance(measure, measure_module.MeasureFloat): + return metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE + else: + raise ValueError + else: + raise AssertionError # pragma: NO COVER + + +def view_to_metric_descriptor(view): + """Get a MetricDescriptor for given view data. + + :type view: (:class: '~opencensus.stats.view.View') + :param view: the view data to for which to build a metric descriptor + """ + return metric_descriptor.MetricDescriptor( + view.name, view.description, view.measure.unit, + get_metric_type(view.measure, view.aggregation), + # TODO: add label key description + [label_key.LabelKey(tk, "") for tk in view.columns]) diff --git a/tests/unit/stats/test_metric_utils.py b/tests/unit/stats/test_metric_utils.py new file mode 100644 index 000000000..5247344fb --- /dev/null +++ b/tests/unit/stats/test_metric_utils.py @@ -0,0 +1,102 @@ +# Copyright 2018, OpenCensus Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +try: + import mock +except ImportError: + from unittest import mock + +import unittest + +from opencensus.metrics.export import metric_descriptor +from opencensus.stats import aggregation +from opencensus.stats import measure +from opencensus.stats import metric_utils +from opencensus.stats import view + + +class TestMetricUtils(unittest.TestCase): + def test_get_metric_type(self): + measure_int = mock.Mock(spec=measure.MeasureInt) + measure_float = mock.Mock(spec=measure.MeasureFloat) + agg_sum = mock.Mock(spec=aggregation.SumAggregation) + agg_sum.aggregation_type = aggregation.Type.SUM + agg_count = mock.Mock(spec=aggregation.CountAggregation) + agg_count.aggregation_type = aggregation.Type.COUNT + agg_dist = mock.Mock(spec=aggregation.DistributionAggregation) + agg_dist.aggregation_type = aggregation.Type.DISTRIBUTION + agg_lv = mock.Mock(spec=aggregation.LastValueAggregation) + agg_lv.aggregation_type = aggregation.Type.LASTVALUE + + view_to_metric_type = { + (measure_int, agg_sum): + metric_descriptor.MetricDescriptorType.CUMULATIVE_INT64, + (measure_int, agg_count): + metric_descriptor.MetricDescriptorType.CUMULATIVE_INT64, + (measure_int, agg_dist): + metric_descriptor.MetricDescriptorType.CUMULATIVE_DISTRIBUTION, + (measure_int, agg_lv): + metric_descriptor.MetricDescriptorType.GAUGE_INT64, + (measure_float, agg_sum): + metric_descriptor.MetricDescriptorType.CUMULATIVE_DOUBLE, + (measure_float, agg_count): + metric_descriptor.MetricDescriptorType.CUMULATIVE_INT64, + (measure_float, agg_dist): + metric_descriptor.MetricDescriptorType.CUMULATIVE_DISTRIBUTION, + (measure_float, agg_lv): + metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE, + } + + for (mm, ma), metric_type in view_to_metric_type.items(): + self.assertEqual(metric_utils.get_metric_type(mm, ma), metric_type) + + def test_get_metric_type_bad_aggregation(self): + base_agg = mock.Mock(spec=aggregation.BaseAggregation) + base_agg.aggregation_type = aggregation.Type.NONE + with self.assertRaises(ValueError): + metric_utils.get_metric_type(mock.Mock(), base_agg) + + bad_agg = mock.Mock(spec=aggregation.SumAggregation) + bad_agg.aggregation_type = aggregation.Type.COUNT + with self.assertRaises(AssertionError): + metric_utils.get_metric_type(mock.Mock(), bad_agg) + + def test_get_metric_type_bad_measure(self): + base_measure = mock.Mock(spec=measure.BaseMeasure) + agg_sum = mock.Mock(spec=aggregation.SumAggregation) + agg_sum.aggregation_type = aggregation.Type.SUM + agg_lv = mock.Mock(spec=aggregation.LastValueAggregation) + agg_lv.aggregation_type = aggregation.Type.LASTVALUE + with self.assertRaises(ValueError): + metric_utils.get_metric_type(base_measure, agg_sum) + with self.assertRaises(ValueError): + metric_utils.get_metric_type(base_measure, agg_lv) + + def test_view_to_metric_descriptor(self): + mock_measure = mock.Mock(spec=measure.MeasureFloat) + mock_agg = mock.Mock(spec=aggregation.SumAggregation) + mock_agg.aggregation_type = aggregation.Type.SUM + test_view = view.View("name", "description", ["tk1", "tk2"], + mock_measure, mock_agg) + + md = metric_utils.view_to_metric_descriptor(test_view) + self.assertTrue(isinstance(md, metric_descriptor.MetricDescriptor)) + self.assertEqual(md.name, test_view.name) + self.assertEqual(md.description, test_view.description) + self.assertEqual(md.unit, test_view.measure.unit) + self.assertEqual( + md.type, metric_descriptor.MetricDescriptorType.CUMULATIVE_DOUBLE) + self.assertTrue( + all(lk.key == col + for lk, col in zip(md.label_keys, test_view.columns)))