osmo_ctrl.py: add RateCounters

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

Change-Id: Id799b3bb81eb9c04d13c26ff611e40363920300e
This commit is contained in:
Neels Hofmeyr 2020-11-30 22:04:26 +01:00
parent 012a17da6a
commit 5354058c75
5 changed files with 453 additions and 0 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -238,4 +238,230 @@ class OsmoCtrl(log.Origin):
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