class Delay(NumericOption): _basetype = PQ _maximum = PQ("3000ms") _attributes = ( ("jitter", PQ, PQ(0.0, "us")), ("correlation", Percent, Percent(0.0)), ) def __float__(self): return float(self._value.inUnitsOf("ms"))
def parse_report(bigstring): imps = [] for line in bigstring.splitlines(): if line.startswith("qdisc"): if "netem" in line: if "delay" in line: delay = PQ(0.0, "ms") jitter = PQ(0.0, "ms") correlation = 0.0 mo = re.search( r"delay ([0-9.]+\s*\D+)\s+([0-9.]+\s*\D+)\s+([0-9.]+)%", # noqa line) if mo: delay = PQ(mo.group(1)) jitter = PQ(mo.group(2)) correlation = Percent(mo.group(3)) else: mo = re.search( r"delay ([0-9.]+\s*\D+)\s+([0-9.]+\s*\D+)", line) if mo: delay = PQ(mo.group(1)) jitter = PQ(mo.group(2)) else: mo = re.search(r"delay ([0-9.]+\s*\D+)", line) if mo: delay = PQ(mo.group(1)) imps.append(Delay(delay, jitter=jitter, correlation=correlation)) if "distribution" in line: mo = re.search(r"distribution (\w+)", line) dist = mo.group(1) imps.append(Distribution(dist)) if "loss" in line: correlation = 0.0 mo = re.search(r"loss\s+([0-9.]+)%\s+([0-9.]+)%", line) if mo: pstr = mo.group(1) correlation = mo.group(2) else: mo = re.search(r"loss\s+([0-9.]+)%", line) if mo: pstr = mo.group(1) imps.append(Drop(pstr, correlation=correlation)) if "corrupt" in line: correlation = 0.0 mo = re.search(r"corrupt\s+([0-9.]+)%\s+([0-9.]+)%", line) if mo: pstr = mo.group(1) correlation = mo.group(2) else: mo = re.search(r"corrupt\s+([0-9.]+)%", line) if mo: pstr = mo.group(1) imps.append(Corrupt(pstr, correlation=correlation)) if "duplicate" in line: correlation = 0.0 mo = re.search(r"duplicate\s+([0-9.]+)%\s+([0-9.]+)%", line) if mo: pstr = mo.group(1) correlation = mo.group(2) else: mo = re.search(r"duplicate\s+([0-9.]+)%", line) if mo: pstr = mo.group(1) imps.append(Duplicate(pstr, correlation=correlation)) if "reorder" in line: correlation = 0.0 mo = re.search(r"reorder\s+([0-9.]+)%\s+([0-9.]+)%", line) if mo: pstr = mo.group(1) correlation = mo.group(2) else: mo = re.search(r"reorder\s+([0-9.]+)%", line) if mo: pstr = mo.group(1) imps.append(Reorder(pstr, correlation=correlation)) if "gap" in line: mo = re.search(r"gap\s+([0-9]+)", line) pstr = mo.group(1) imps.append(Gap(pstr)) return Impairment(*imps)
class Impairment: # name of maximum values and default values _max_values = { "maxlatency": PQ("3000ms"), "maxdrop": Percent(90.0), "maxcorruption": Percent(90.0), "maxduplicate": Percent(90.0), "maxreorder": Percent(90.0), "maxgap": Gap, } def __init__(self, *args, **kwargs): self._parent = None self._children = [] self._nodeid = "root" self._impairments = list(args) for maxname, default in list(Impairment._max_values.items()): value = kwargs.pop(maxname, default) setattr(self, maxname, value) if kwargs: raise ValueError( "Impairment got extra keyword arguments: {!r}".format(kwargs)) def add_delay(self, value, **kwargs): return self._impairments.append(Delay(value, **kwargs)) def add_distribution(self, value): return self._impairments.append(Distribution(value)) def add_drop(self, value, **kwargs): return self._impairments.append(Drop(value, **kwargs)) def add_corrupt(self, value, **kwargs): return self._impairments.append(Corrupt(value, **kwargs)) def add_duplicate(self, value, **kwargs): return self._impairments.append(Duplicate(value, **kwargs)) def add_reorder(self, value, **kwargs): return self._impairments.append(Reorder(value, **kwargs)) def add_gap(self, value): return self._impairments.append(Gap(value)) def add_limit(self, value): return self._impairments.append(Limit(value)) def over_maximum(self): primary = self._impairments[0] return primary.over_maximum() def __str__(self): s = [self._nodeid if isinstance(self._nodeid, str) else "{:04x}".format(self._nodeid)] s.append("netem") s.extend(str(imp) for imp in self._impairments) return " ".join(s) def copy(self): return self.__class__(**self.__dict__) def clear(self): self._impairments = [] def __repr__(self): return "{0}({1})".format( self.__class__.__name__, ", ".join(repr(o) for o in self._impairments)) def __iter__(self): return iter(self._impairments) def __getitem__(self, idx): return self._impairments[idx] def __bool__(self): return bool(self._impairments) def __add__(self, other): return self._perform_op(other, operator.add) def __sub__(self, other): return self._perform_op(other, operator.sub) def __truediv__(self, other): return self._perform_op(other, operator.truediv) __div__ = __truediv__ def __mul__(self, other): return self._perform_op(other, operator.mul) def _perform_op(self, other, op): new = [] if not self._impairments: return self.__class__() imp = self._impairments[0] if isinstance(imp, NumericOption): new.append(op(imp, other[0])) else: new.append(imp.copy()) for imp in self._impairments[1:]: new.append(imp.copy()) return self.__class__(*new) def applyto(self, router): """Apply impairments from this object using given router instance. The impairments values are spread over all of the router interfaces to make it symetrical. Typically, this will be two interfaces. """ # e.g.: tc qdisc add dev {0} root netem delay {1}ms {2} {3} {4} if self._parent is not None: raise RouterConstructorError("May only apply to root impairment.") oldimp = router.current() router.reset() if not self._impairments: return n = len(router.interfaces) parms = self.netem_command(n) if self._nodeid == "root": change = "add" else: change = "change" for intf in router.interfaces: cmd = "tc qdisc {} dev {} {} {}".format(change, intf, self._nodeid, parms) router._perform(cmd) # TODO apply child impairments return oldimp def netem_command(self, n=1): s = ["netem"] for imp in self._impairments: if isinstance(imp, NumericOption): s.append(str(imp / n)) else: s.append(str(imp)) return " ".join(s) def get_direction(self, oldvalue): if not oldvalue: return Unknown assert type(oldvalue) is type(self) oldvalue = oldvalue._impairments[0] value = self._impairments[0] if oldvalue > value: return Down elif oldvalue < value: return Up else: return Level # tree management @property def parent(self): return self._parent def _check_obj(self, other): if not isinstance(other, self.__class__): raise ValueError("Must operate on same Impairment type.") def replace(self, imp): self._check_obj(imp) if self._parent: p = self._parent i = self._parent.index(self) del self._parent[i] self._parent = None p.insert(i, imp) return self def detach(self): if self._parent: try: i = self._parent.index(self) del self._parent[i] except ValueError: pass self._parent = None return self def destroy(self): if self._parent: i = self._parent.index(self) del self._parent[i] self._parent = None for n in self._children: n._parent = None self._children = None def get_children(self): return self._children[:] def _find_index(self, index): if type(index) is str: for i, ch in enumerate(self._children): if ch.matchpath(index): return i raise IndexError("no impairments match") else: return index _path_re = re.compile(r'(\w+)\[(.*)]') def matchpath(self, pathelement): if "[" not in pathelement: return pathelement == self._name else: mo = Impairment._path_re.match(pathelement) if mo: name, match = mo.groups() if name != self._name: return False mp = match.split("=") attr = getattr(self, mp[0], None) if attr is None: return False if len(mp) > 1: return mp[1][1:-1] == attr else: return True else: raise ValueError( "Path element {!r} not found.".format(pathelement))