Change in osmo-gsm-tester[master]: osmo_ctrl.py: add RateCounters

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

neels gerrit-no-reply at lists.osmocom.org
Thu Dec 10 23:42:14 UTC 2020


neels has submitted this change. ( https://gerrit.osmocom.org/c/osmo-gsm-tester/+/21522 )

Change subject: osmo_ctrl.py: add RateCounters
......................................................................

osmo_ctrl.py: add RateCounters

First user will be the upcoming handover_2G/handover.py test in
I0b2671304165a1aaae2b386af46fbd8b098e3bd8.

Change-Id: Id799b3bb81eb9c04d13c26ff611e40363920300e
---
A selftest/rate_ctrs_test/_prep.py
A selftest/rate_ctrs_test/rate_ctrs_test.err
A selftest/rate_ctrs_test/rate_ctrs_test.ok
A selftest/rate_ctrs_test/rate_ctrs_test.py
M src/osmo_gsm_tester/obj/osmo_ctrl.py
5 files changed, 453 insertions(+), 0 deletions(-)



diff --git a/selftest/rate_ctrs_test/_prep.py b/selftest/rate_ctrs_test/_prep.py
new file mode 100644
index 0000000..773f190
--- /dev/null
+++ b/selftest/rate_ctrs_test/_prep.py
@@ -0,0 +1,16 @@
+import sys, os
+
+script_dir = sys.path[0]
+top_dir = os.path.join(script_dir, '..', '..')
+src_dir = os.path.join(top_dir, 'src')
+
+# to find the osmo_gsm_tester py module
+sys.path.append(src_dir)
+
+from osmo_gsm_tester.core import log
+
+log.TestsTarget()
+log.set_all_levels(log.L_DBG)
+
+if '-v' in sys.argv:
+    log.style_change(trace=True)
diff --git a/selftest/rate_ctrs_test/rate_ctrs_test.err b/selftest/rate_ctrs_test/rate_ctrs_test.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/selftest/rate_ctrs_test/rate_ctrs_test.err
diff --git a/selftest/rate_ctrs_test/rate_ctrs_test.ok b/selftest/rate_ctrs_test/rate_ctrs_test.ok
new file mode 100644
index 0000000..489f58f
--- /dev/null
+++ b/selftest/rate_ctrs_test/rate_ctrs_test.ok
@@ -0,0 +1,155 @@
+- empty RateCounters()
+| 
+- initialized RateCounters, single var
+| rate_ctr.abs.inst.0.var = 0
+- incremented inst.var
+| rate_ctr.abs.inst.0.var = 1
+- incremented inst.var again
+| rate_ctr.abs.inst.0.var = 2
+- incremented inst.var by 5
+| rate_ctr.abs.inst.0.var = 7
+- initialized RateCounters, two vars
+| rate_ctr.abs.inst.0.foo = 0
+| rate_ctr.abs.inst.0.var = 0
+- incremented foo and var
+| rate_ctr.abs.inst.0.foo = 1
+| rate_ctr.abs.inst.0.var = 1
+- incremented var again
+| rate_ctr.abs.inst.0.foo = 1
+| rate_ctr.abs.inst.0.var = 2
+- incremented foo by 5
+| rate_ctr.abs.inst.0.foo = 6
+| rate_ctr.abs.inst.0.var = 2
+- initialized RateCounters, two vars, three instances
+| rate_ctr.abs.inst.0.foo = 0
+| rate_ctr.abs.inst.0.var = 0
+| rate_ctr.abs.inst.1.foo = 0
+| rate_ctr.abs.inst.1.var = 0
+| rate_ctr.abs.inst.2.foo = 0
+| rate_ctr.abs.inst.2.var = 0
+- incremented foo and var on separate instances
+| rate_ctr.abs.inst.0.foo = 1
+| rate_ctr.abs.inst.0.var = 0
+| rate_ctr.abs.inst.1.foo = 0
+| rate_ctr.abs.inst.1.var = 1
+| rate_ctr.abs.inst.2.foo = 0
+| rate_ctr.abs.inst.2.var = 0
+- incremented var on instance 2
+| rate_ctr.abs.inst.0.foo = 1
+| rate_ctr.abs.inst.0.var = 0
+| rate_ctr.abs.inst.1.foo = 0
+| rate_ctr.abs.inst.1.var = 1
+| rate_ctr.abs.inst.2.foo = 0
+| rate_ctr.abs.inst.2.var = 1
+- incremented foo by 5 on instances 1,2
+| rate_ctr.abs.inst.0.foo = 1
+| rate_ctr.abs.inst.0.var = 0
+| rate_ctr.abs.inst.1.foo = 5
+| rate_ctr.abs.inst.1.var = 1
+| rate_ctr.abs.inst.2.foo = 5
+| rate_ctr.abs.inst.2.var = 1
+- copy
+| rate_ctr.abs.inst.0.foo = 1
+| rate_ctr.abs.inst.0.var = 0
+| rate_ctr.abs.inst.1.foo = 5
+| rate_ctr.abs.inst.1.var = 1
+| rate_ctr.abs.inst.2.foo = 5
+| rate_ctr.abs.inst.2.var = 1
+- increment two vars by 100 on all three instances
+| rate_ctr.abs.inst.0.foo = 101
+| rate_ctr.abs.inst.0.var = 100
+| rate_ctr.abs.inst.1.foo = 105
+| rate_ctr.abs.inst.1.var = 101
+| rate_ctr.abs.inst.2.foo = 105
+| rate_ctr.abs.inst.2.var = 101
+- subtract original copy
+| rate_ctr.abs.inst.0.foo = 100
+| rate_ctr.abs.inst.0.var = 100
+| rate_ctr.abs.inst.1.foo = 100
+| rate_ctr.abs.inst.1.var = 100
+| rate_ctr.abs.inst.2.foo = 100
+| rate_ctr.abs.inst.2.var = 100
+- add original copy
+| rate_ctr.abs.inst.0.foo = 101
+| rate_ctr.abs.inst.0.var = 100
+| rate_ctr.abs.inst.1.foo = 105
+| rate_ctr.abs.inst.1.var = 101
+| rate_ctr.abs.inst.2.foo = 105
+| rate_ctr.abs.inst.2.var = 101
+- increment types per_hour, per_day by 23
+| rate_ctr.abs.inst.0.foo = 101
+| rate_ctr.abs.inst.0.var = 100
+| rate_ctr.abs.inst.1.foo = 105
+| rate_ctr.abs.inst.1.var = 101
+| rate_ctr.abs.inst.2.foo = 105
+| rate_ctr.abs.inst.2.var = 101
+| rate_ctr.per_day.inst.0.foo = 23
+| rate_ctr.per_day.inst.0.moo = 23
+| rate_ctr.per_day.inst.0.var = 23
+| rate_ctr.per_day.inst.1.foo = 23
+| rate_ctr.per_day.inst.1.moo = 23
+| rate_ctr.per_day.inst.1.var = 23
+| rate_ctr.per_day.inst.2.foo = 23
+| rate_ctr.per_day.inst.2.moo = 23
+| rate_ctr.per_day.inst.2.var = 23
+| rate_ctr.per_hour.inst.0.foo = 23
+| rate_ctr.per_hour.inst.0.moo = 23
+| rate_ctr.per_hour.inst.0.var = 23
+| rate_ctr.per_hour.inst.1.foo = 23
+| rate_ctr.per_hour.inst.1.moo = 23
+| rate_ctr.per_hour.inst.1.var = 23
+| rate_ctr.per_hour.inst.2.foo = 23
+| rate_ctr.per_hour.inst.2.moo = 23
+| rate_ctr.per_hour.inst.2.var = 23
+- copy
+| rate_ctr.abs.inst.0.foo = 101
+| rate_ctr.abs.inst.0.var = 100
+| rate_ctr.abs.inst.1.foo = 105
+| rate_ctr.abs.inst.1.var = 101
+| rate_ctr.abs.inst.2.foo = 105
+| rate_ctr.abs.inst.2.var = 101
+| rate_ctr.per_day.inst.0.foo = 23
+| rate_ctr.per_day.inst.0.moo = 23
+| rate_ctr.per_day.inst.0.var = 23
+| rate_ctr.per_day.inst.1.foo = 23
+| rate_ctr.per_day.inst.1.moo = 23
+| rate_ctr.per_day.inst.1.var = 23
+| rate_ctr.per_day.inst.2.foo = 23
+| rate_ctr.per_day.inst.2.moo = 23
+| rate_ctr.per_day.inst.2.var = 23
+| rate_ctr.per_hour.inst.0.foo = 23
+| rate_ctr.per_hour.inst.0.moo = 23
+| rate_ctr.per_hour.inst.0.var = 23
+| rate_ctr.per_hour.inst.1.foo = 23
+| rate_ctr.per_hour.inst.1.moo = 23
+| rate_ctr.per_hour.inst.1.var = 23
+| rate_ctr.per_hour.inst.2.foo = 23
+| rate_ctr.per_hour.inst.2.moo = 23
+| rate_ctr.per_hour.inst.2.var = 23
+- match?  True
+- increment foo
+| rate_ctr.abs.inst.0.foo = 102
+| rate_ctr.abs.inst.0.var = 100
+| rate_ctr.abs.inst.1.foo = 105
+| rate_ctr.abs.inst.1.var = 101
+| rate_ctr.abs.inst.2.foo = 105
+| rate_ctr.abs.inst.2.var = 101
+| rate_ctr.per_day.inst.0.foo = 23
+| rate_ctr.per_day.inst.0.moo = 23
+| rate_ctr.per_day.inst.0.var = 23
+| rate_ctr.per_day.inst.1.foo = 23
+| rate_ctr.per_day.inst.1.moo = 23
+| rate_ctr.per_day.inst.1.var = 23
+| rate_ctr.per_day.inst.2.foo = 23
+| rate_ctr.per_day.inst.2.moo = 23
+| rate_ctr.per_day.inst.2.var = 23
+| rate_ctr.per_hour.inst.0.foo = 23
+| rate_ctr.per_hour.inst.0.moo = 23
+| rate_ctr.per_hour.inst.0.var = 23
+| rate_ctr.per_hour.inst.1.foo = 23
+| rate_ctr.per_hour.inst.1.moo = 23
+| rate_ctr.per_hour.inst.1.var = 23
+| rate_ctr.per_hour.inst.2.foo = 23
+| rate_ctr.per_hour.inst.2.moo = 23
+| rate_ctr.per_hour.inst.2.var = 23
+- match?  False
diff --git a/selftest/rate_ctrs_test/rate_ctrs_test.py b/selftest/rate_ctrs_test/rate_ctrs_test.py
new file mode 100755
index 0000000..935bd9d
--- /dev/null
+++ b/selftest/rate_ctrs_test/rate_ctrs_test.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+import _prep
+
+from osmo_gsm_tester.obj.osmo_ctrl import *
+
+rc = RateCounters()
+print('- empty RateCounters()' + rc.str())
+
+rc = RateCounters('inst', 'var')
+print('- initialized RateCounters, single var' + rc.str())
+rc.inc('inst', 'var')
+print('- incremented inst.var' + rc.str())
+rc.inc('inst', 'var')
+print('- incremented inst.var again' + rc.str())
+rc.inc('inst', 'var', 5)
+print('- incremented inst.var by 5' + rc.str())
+
+rc = RateCounters('inst', ('foo', 'var'))
+print('- initialized RateCounters, two vars' + rc.str())
+rc.inc('inst', ('foo', 'var'))
+print('- incremented foo and var' + rc.str())
+rc.inc('inst', 'var')
+print('- incremented var again' + rc.str())
+rc.inc('inst', 'foo', 5)
+print('- incremented foo by 5' + rc.str())
+
+rc = RateCounters('inst', ('foo', 'var'), instances=range(3))
+print('- initialized RateCounters, two vars, three instances' + rc.str())
+rc.inc('inst', 'foo', instances=0)
+rc.inc('inst', 'var', instances=1)
+print('- incremented foo and var on separate instances' + rc.str())
+rc.inc('inst', 'var', instances=2)
+print('- incremented var on instance 2' + rc.str())
+rc.inc('inst', 'foo', 5, instances=(1,2))
+print('- incremented foo by 5 on instances 1,2' + rc.str())
+
+rc_rel = rc.copy()
+print('- copy' + rc_rel.str())
+rc.inc('inst', ('foo', 'var'), 100, instances=range(3))
+print('- increment two vars by 100 on all three instances' + rc.str())
+rc.subtract(rc_rel)
+print('- subtract original copy' + rc.str())
+rc.add(rc_rel)
+print('- add original copy' + rc.str())
+
+rc.inc('inst', ('foo', 'var', 'moo'), 23, instances=range(3), kinds=('per_hour', 'per_day'))
+print('- increment types per_hour, per_day by 23' + rc.str())
+
+rc2 = rc.copy()
+print('- copy' + rc2.str())
+print('- match? ', (rc == rc2))
+rc2.inc('inst', 'foo')
+print('- increment foo' + rc2.str())
+print('- match? ', (rc == rc2))
+
+# vim: expandtab tabstop=4 shiftwidth=4
diff --git a/src/osmo_gsm_tester/obj/osmo_ctrl.py b/src/osmo_gsm_tester/obj/osmo_ctrl.py
index 644025f..6c4ac87 100644
--- a/src/osmo_gsm_tester/obj/osmo_ctrl.py
+++ b/src/osmo_gsm_tester/obj/osmo_ctrl.py
@@ -238,4 +238,230 @@
     def __exit__(self, *exc_info):
         self.disconnect()
 
+class RateCountersExn(log.Error):
+    pass
+
+class RateCounters(dict):
+    '''Usage example:
+        counter_names = (
+                'handover:completed',
+                'handover:stopped',
+                'handover:no_channel',
+                'handover:timeout',
+                'handover:failed',
+                'handover:error',
+                )
+
+        # initialize the listing of CTRL vars of the counters to watch.
+        # First on the 'bsc' node:
+        #   rate_ctr.abs.bsc.0.handover:completed
+        #   rate_ctr.abs.bsc.0.handover:stopped
+        #   ...
+        counters = RateCounters('bsc', counter_names, from_ctrl=bsc.ctrl)
+
+        # And also add counters for two 'bts' instances:
+        #   rate_ctr.abs.bts.0.handover:completed
+        #   rate_ctr.abs.bts.0.handover:stopped
+        #   ...
+        #   rate_ctr.abs.bts.1.handover:completed
+        #   ...
+        counters.add(RateCounters('bts', counter_names, instances=(0, 1)))
+
+        # read initial counter values, from the bsc_ctrl, as set in
+        # counters.from_ctrl in the RateCounters() constructor above.
+        counters.read()
+
+        # Do some actions that should increment counters in the SUT
+        do_a_handover()
+
+        if approach_without_wait:
+            # increment the counters as expected
+            counters.inc('bts', 'handover:completed')
+
+            # read counters from CTRL again, and fail if they differ
+            counters.verify()
+
+        if approach_with_wait:
+            # you can wait for counters to change. counters.changed() does not
+            # modify counters' values, just reads values from CTRL and stores
+            # the changes in counters.diff.
+            wait(counters.changed, timeout=20)
+
+            # log which counters changed by how much, found in counters.diff
+            # after each counters.changed() call:
+            print(counters.diff.str(skip_zero_vals=True))
+
+            if check_all_vals:
+                # Assert all values:
+                expected_diff = counters.copy().clear()
+                expected_diff.inc('bts', 'handover:completed', instances=(0, 1))
+                counters.diff.expect(expected_diff)
+            else:
+                # Assert only some specific counters:
+                expected_diff = RateCounters()
+                expected_diff.inc('bts', 'handover:completed', instances=(0, 1))
+                counters.diff.expect(expected_diff)
+
+            # update counters to the last read values if desired
+            counters.add(counters.diff)
+    '''
+
+    def __init__(self, instance_names=(), counter_names=(), instances=0, kinds='abs', init_val=0, from_ctrl=None):
+        def init_cb(var):
+            self[var] = init_val
+        RateCounters.for_each(init_cb, instance_names, counter_names, instances, kinds, results=False)
+        self.from_ctrl = from_ctrl
+        self.diff = None
+
+    @staticmethod
+    def for_each(callback_func, instance_names, counter_names, instances=0, kinds='abs', results=True):
+        '''Call callback_func for a set of rate counter var names, mostly
+           called by more convenient functions. See inc() for a comprehensive
+           explanation.
+        '''
+        if type(instance_names) is str:
+            instance_names = (instance_names, )
+        if type(counter_names) is str:
+            counter_names = (counter_names, )
+        if type(kinds) is str:
+            kinds = (kinds, )
+        if type(instances) is int:
+            instances = (instances, )
+        if results is True:
+            results = RateCounters()
+        elif results is False:
+            results = None
+        for instance_name in instance_names:
+            for instance_nr in instances:
+                for counter_name in counter_names:
+                    for kind in kinds:
+                        var = 'rate_ctr.{kind}.{instance_name}.{instance_nr}.{counter_name}'.format(**locals())
+                        result = callback_func(var)
+                        if results is not None:
+                            results[var] = result
+        return results
+
+    def __str__(self):
+        return self.str(', ', '')
+
+    def str(self, sep='\n| ', prefix='\n| ', vals=None, skip_zero_vals=False):
+        '''The 'vals' arg is useful to print a plain dict() of counter values like a RateCounters class.
+           By default print self.'''
+        if vals is None:
+            vals = self
+        return prefix + sep.join('%s = %d' % (var, val) for var, val in sorted(vals.items())
+                                 if (not skip_zero_vals) or (val != 0))
+
+    def inc(self, instance_names, counter_names, inc=1, instances=0, kinds='abs'):
+        '''Increment a set of counters.
+           inc('xyz', 'val')             --> rate_ctr.abs.xyz.0.val += 1
+
+           inc('xyz', ('foo', 'bar'))    --> rate_ctr.abs.xyz.0.foo += 1
+                                             rate_ctr.abs.xyz.0.bar += 1
+
+           inc(('xyz', 'pqr'), 'val')    --> rate_ctr.abs.xyz.0.val += 1
+                                             rate_ctr.abs.pqr.0.val += 1
+
+           inc('xyz', 'val', instances=range(3))
+                                         --> rate_ctr.abs.xyz.0.val += 1
+                                             rate_ctr.abs.xyz.1.val += 1
+                                             rate_ctr.abs.xyz.2.val += 1
+        '''
+        def inc_cb(var):
+            val = self.get(var, 0)
+            val += inc
+            self[var] = val
+            return val
+        RateCounters.for_each(inc_cb, instance_names, counter_names, instances, kinds, results=False)
+        return self
+
+    def add(self, rate_counters):
+        '''Add the given values up to the values in self.
+           rate_counters can be a RateCounters instance or a plain dict of CTRL
+           var as key and counter integer as value.
+        '''
+        for var, add_val in rate_counters.items():
+            val = self.get(var, 0)
+            val += add_val
+            self[var] = val
+        return self
+
+    def subtract(self, rate_counters):
+        '''Same as add(), but subtract values from self instead.
+           Useful to verify counters relative to an arbitrary reference.'''
+        for var, subtract_val in rate_counters.items():
+            val = self.get(var, 0)
+            val -= subtract_val
+            self[var] = val
+        return self
+
+
+    def clear(self, val=0):
+        '''Set all counts to 0 (or a specific value)'''
+        for var in self.keys():
+            self[var] = val
+        return self
+
+    def copy(self):
+        '''Return a copy of all keys and values stored in self.'''
+        cpy = RateCounters(from_ctrl = self.from_ctrl)
+        cpy.update(self)
+        return cpy
+
+    def read(self):
+        '''Read all counters from the CTRL connection passed to RateCounters(from_ctrl=x).
+           The CTRL must be connected, e.g.
+           with bsc.ctrl() as ctrl:
+               counters = RateCounters(ctrl)
+               counters.read()
+        '''
+        for var in self.keys():
+            self[var] = self.from_ctrl.get_int_var(var)
+        self.from_ctrl.dbg('Read counters:', self.str())
+        return self
+
+    def verify(self):
+        '''Read counters from CTRL and assert that they match the current counts'''
+        got_vals = self.copy()
+        got_vals.read()
+        got_vals.expect(self)
+
+    def changed(self):
+        '''Read counters from CTRL, and return True if anyone is different now.
+           Store the difference in counts in self.diff (replace self.diff for
+           each changed() call). The counts in self are never modified.'''
+        self.diff = None
+        got_vals = self.copy()
+        got_vals.read()
+        if self != got_vals:
+            self.diff = got_vals
+            self.diff.subtract(self)
+            self.from_ctrl.dbg('Changed counters:', self.diff.str(skip_zero_vals=True))
+            return True
+        return False
+
+    def expect(self, expect_vals):
+        '''Iterate expect_vals and fail if any counter value differs from self.
+           expect_vals can be a RateCounters instance or a plain dict of CTRL
+           var as key and counter integer as value.
+        '''
+        ok = 0
+        errs = []
+        for var, expect_val in expect_vals.items():
+            got_val = self.get(var)
+            if got_val is None:
+                errs.append('expected {var} == {expect_val}, but no such value found'.format(**locals()))
+                continue
+            if got_val != expect_val:
+                errs.append('expected {var} == {expect_val}, but is {got_val}'.format(**locals()))
+                continue
+            ok += 1
+        if errs:
+            self.from_ctrl.dbg('Expected rate counters:', self.str(vals=expect_vals))
+            self.from_ctrl.dbg('Got rate counters:', self.str())
+            raise RateCountersExn('%d of %d rate counters mismatch:' % (len(errs), len(errs) + ok), '\n| ' + '\n| '.join(errs))
+        else:
+            self.from_ctrl.log('Verified %d rate counters' % ok)
+            self.from_ctrl.dbg('Verified %d rate counters:' % ok, expect_vals)
+
 # vim: expandtab tabstop=4 shiftwidth=4

-- 
To view, visit https://gerrit.osmocom.org/c/osmo-gsm-tester/+/21522
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-gsm-tester
Gerrit-Branch: master
Gerrit-Change-Id: Id799b3bb81eb9c04d13c26ff611e40363920300e
Gerrit-Change-Number: 21522
Gerrit-PatchSet: 4
Gerrit-Owner: neels <nhofmeyr at sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: pespin <pespin at sysmocom.de>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20201210/396ed9da/attachment.htm>


More information about the gerrit-log mailing list