blob: bbee3baaf850c28f15835f1387288df1cf7ee912 [file]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
#
# pytype: skip-file
import pickle
import unittest
import apache_beam as beam
from apache_beam.metrics.cells import DistributionData
from apache_beam.metrics.execution import MetricKey
from apache_beam.metrics.execution import MetricsContainer
from apache_beam.metrics.execution import MetricsEnvironment
from apache_beam.metrics.execution import MetricUpdater
from apache_beam.metrics.metric import Metrics
from apache_beam.metrics.metric import MetricsFilter
from apache_beam.metrics.metricbase import MetricName
from apache_beam.metrics.metrics_flag import MetricsFlag
from apache_beam.options.pipeline_options import PipelineOptions
from apache_beam.runners.worker import statesampler
from apache_beam.testing.test_pipeline import TestPipeline
from apache_beam.testing.util import assert_that
from apache_beam.testing.util import equal_to
from apache_beam.utils import counters
class MetricsFlagTest(unittest.TestCase):
"""Covers the disable*Metrics experiments.
See https://github.com/apache/beam/issues/38746.
"""
def setUp(self):
MetricsFlag.reset()
self.sampler = statesampler.StateSampler('', counters.CounterFactory())
statesampler.set_current_tracker(self.sampler)
self.state = self.sampler.scoped_state(
'mystep', 'myState', metrics_container=MetricsContainer('mystep'))
self.sampler.start()
def tearDown(self):
self.sampler.stop()
MetricsFlag.reset()
@staticmethod
def _set_experiments(*experiments):
MetricsFlag.set_default_pipeline_options(
PipelineOptions(['--experiments=%s' % exp for exp in experiments]))
def test_flags_follow_experiments(self):
self.assertFalse(MetricsFlag.counter_disabled())
self.assertFalse(MetricsFlag.string_set_disabled())
self.assertFalse(MetricsFlag.bounded_trie_disabled())
for experiment, expected in [
('disableCounterMetrics', (True, False, False)),
('disableStringSetMetrics', (False, True, False)),
('disableBoundedTrieMetrics', (False, False, True)),
]:
MetricsFlag.reset()
self._set_experiments(experiment)
self.assertEqual((
MetricsFlag.counter_disabled(),
MetricsFlag.string_set_disabled(),
MetricsFlag.bounded_trie_disabled()),
expected,
experiment)
MetricsFlag.reset()
self._set_experiments(
'disableCounterMetrics',
'disableStringSetMetrics',
'disableBoundedTrieMetrics')
self.assertTrue(MetricsFlag.counter_disabled())
self.assertTrue(MetricsFlag.string_set_disabled())
self.assertTrue(MetricsFlag.bounded_trie_disabled())
def test_first_call_wins(self):
self._set_experiments('disableCounterMetrics')
# Later options, e.g. from user code constructing a Pipeline on a worker,
# do not change the flags the harness was started with.
self._set_experiments('disableStringSetMetrics')
self.assertTrue(MetricsFlag.counter_disabled())
self.assertFalse(MetricsFlag.string_set_disabled())
def test_update_call_shapes_keep_working(self):
# The first attempt at this feature (#38749) was reverted because it
# changed the signature of DelegatingCounter.inc; the metric objects must
# stay MetricUpdater callables that accept the value as a keyword too.
with self.state:
counter = Metrics.counter('ns', 'counter')
self.assertIsInstance(counter.inc, MetricUpdater)
counter.inc()
counter.inc(4)
counter.inc(value=5)
counter.dec()
counter.dec(2)
string_set = Metrics.string_set('ns', 'set')
string_set.add('a')
string_set.add(value='b')
container = MetricsEnvironment.current_container()
self.assertEqual(
container.get_counter(MetricName('ns', 'counter')).get_cumulative(),
7)
self.assertEqual(
container.get_string_set(MetricName(
'ns', 'set')).get_cumulative().string_set, {'a', 'b'})
def test_disabled_counter_is_noop(self):
with self.state:
container = MetricsEnvironment.current_container()
Metrics.counter('ns', 'before').inc()
self.assertEqual(len(container.metrics), 1)
self._set_experiments('disableCounterMetrics')
created_before = Metrics.counter('ns', 'before')
created_before.inc()
created_before.inc(value=5)
created_before.dec()
Metrics.counter('ns', 'after').inc(3)
self.assertEqual(len(container.metrics), 1)
self.assertEqual(
container.get_counter(MetricName('ns', 'before')).get_cumulative(), 1)
# Other kinds keep reporting.
Metrics.distribution('ns', 'dist').update(3)
Metrics.gauge('ns', 'gauge').set(2)
Metrics.string_set('ns', 'set').add('x')
Metrics.bounded_trie('ns', 'trie').add(('x', ))
self.assertEqual(len(container.metrics), 5)
def test_disabled_string_set_is_noop(self):
with self.state:
container = MetricsEnvironment.current_container()
Metrics.string_set('ns', 'before').add('seed')
self.assertEqual(len(container.metrics), 1)
self._set_experiments('disableStringSetMetrics')
Metrics.string_set('ns', 'before').add('more')
Metrics.string_set('ns', 'after').add('value')
self.assertEqual(len(container.metrics), 1)
self.assertEqual(
container.get_string_set(MetricName(
'ns', 'before')).get_cumulative().string_set, {'seed'})
Metrics.counter('ns', 'counter').inc()
self.assertEqual(len(container.metrics), 2)
def test_disabled_bounded_trie_is_noop(self):
with self.state:
container = MetricsEnvironment.current_container()
Metrics.bounded_trie('ns', 'before').add(('a', ))
self.assertEqual(len(container.metrics), 1)
self._set_experiments('disableBoundedTrieMetrics')
Metrics.bounded_trie('ns', 'before').add(('a', 'b'))
Metrics.bounded_trie('ns', 'after').add(('c', ))
self.assertEqual(len(container.metrics), 1)
self.assertEqual(
list(
container.get_bounded_trie(MetricName(
'ns', 'before')).get_cumulative().flattened()),
[('a', False)])
def test_disabled_process_wide_counter_is_noop(self):
self._set_experiments('disableCounterMetrics')
name = MetricName('ns', 'process_wide')
counter = Metrics.DelegatingCounter(name, process_wide=True)
counter.inc()
# The process-wide container is shared with every other test in this
# process, so only check that this counter never reached it.
self.assertNotIn(
MetricKey(None, name),
MetricsEnvironment.process_wide_container().get_cumulative().counters)
def test_disabled_flag_applies_to_unpickled_metrics(self):
# DoFns holding metric objects are pickled at submission time and
# unpickled on the worker, where the harness sets the flags.
counter = Metrics.counter('ns', 'pickled')
counter = pickle.loads(pickle.dumps(counter))
self.assertIsInstance(counter.inc, MetricUpdater)
self._set_experiments('disableCounterMetrics')
with self.state:
counter.inc()
self.assertEqual(len(MetricsEnvironment.current_container().metrics), 0)
def test_disabled_counters_in_pipeline(self):
class SomeDoFn(beam.DoFn):
def process(self, element):
Metrics.counter(self.__class__, 'elements').inc()
Metrics.distribution(self.__class__, 'element_dist').update(element)
yield element
MetricsFlag.reset()
pipeline = TestPipeline(
options=PipelineOptions(['--experiments=disableCounterMetrics']))
results = pipeline | beam.Create([1, 2, 3]) | beam.ParDo(SomeDoFn())
assert_that(results, equal_to([1, 2, 3]))
res = pipeline.run()
res.wait_until_finish()
self.assertEqual(
res.metrics().query(MetricsFilter().with_name('elements'))['counters'],
[])
distributions = res.metrics().query(
MetricsFilter().with_name('element_dist'))['distributions']
self.assertEqual(len(distributions), 1)
self.assertEqual(
distributions[0].committed.data, DistributionData(6, 3, 1, 3))
if __name__ == '__main__':
unittest.main()