diff --git a/included.cfg b/included.cfg index 26a373a..dbd2408 100644 --- a/included.cfg +++ b/included.cfg @@ -8,3 +8,7 @@ net.core.rmem_max = 8388609 [my_included_plugin] type=test +[my_net_plugin] +type=net + + diff --git a/tuned/monitors/base.py b/tuned/monitors/base.py index e5e17b1..3e01781 100644 --- a/tuned/monitors/base.py +++ b/tuned/monitors/base.py @@ -56,13 +56,14 @@ class Monitor(object): self._init_class() assert hasattr(self, "_class_initialized") + self._register_instance(self) + if devices is not None: self.devices = devices else: self.devices = self.get_available_devices() self.update() - self._register_instance(self) def __del__(self): try: diff --git a/tuned/monitors/monitor_net.py b/tuned/monitors/monitor_net.py new file mode 100644 index 0000000..4bb2a66 --- /dev/null +++ b/tuned/monitors/monitor_net.py @@ -0,0 +1,64 @@ +import tuned.monitors +import os +import re +from tuned.utils.nettool import ethcard + +class NetMonitor(tuned.monitors.Monitor): + + @classmethod + def _init_available_devices(cls): + available = [] + for root, dirs, files in os.walk("/sys/devices"): + if root.endswith("/net") and not root.endswith("/virtual/net"): + available += dirs + + cls._available_devices = set(available) + + for dev in available: + max_speed = cls._calcspeed(ethcard(dev).get_max_speed()) + cls._load[dev] = {} + cls._load[dev]["new"] = ['0', '0', '0', '0'] + cls._load[dev]["max"] = [max_speed, 1, max_speed, 1] + cls._updateStat(dev) + cls._load[dev]["max"] = [max_speed, 1, max_speed, 1] + + @classmethod + def _calcspeed(cls, speed): + # 0.6 is just a magical constant (empirical value): Typical workload on netcard won't exceed + # that and if it does, then the code is smart enough to adapt it. + # 1024 * 1024 as for MB -> B + # speed / 8 Mb -> MB + return (int) (0.6 * 1024 * 1024 * speed / 8) + + @classmethod + def _calcdiff(cls, dev): + l = [] + for i in xrange(len(cls._load[dev]["old"])): + l.append(int(cls._load[dev]["new"][i]) - int(cls._load[dev]["old"][i])) + return l + + @classmethod + def _updateStat(cls, dev): + cls._load[dev]["old"] = cls._load[dev]["new"][:] + l = open("/sys/class/net/"+dev+"/statistics/rx_bytes", "r").read().strip() + cls._load[dev]["new"][0] = l + l = open("/sys/class/net/"+dev+"/statistics/rx_packets", "r").read().strip() + cls._load[dev]["new"][1] = l + l = open("/sys/class/net/"+dev+"/statistics/tx_bytes", "r").read().strip() + cls._load[dev]["new"][2] = l + l = open("/sys/class/net/"+dev+"/statistics/tx_packets", "r").read().strip() + cls._load[dev]["new"][3] = l + l = cls._calcdiff(dev) + for i in xrange(len(l)): + if l[i] > cls._load[dev]["max"][i]: + cls._load[dev]["max"][i] = l[i] + cls._load[dev]["diff"] = cls._calcdiff(dev) + + cls._load[dev]["READ"] = float(cls._load[dev]["diff"][0]) / float(cls._load[dev]["max"][0]) + cls._load[dev]["WRITE"] = float(cls._load[dev]["diff"][2]) / float(cls._load[dev]["max"][2]) + + @classmethod + def update(cls): + for device in cls._updating_devices: + cls._updateStat(device) + diff --git a/tuned/plugins/plugin_net.py b/tuned/plugins/plugin_net.py new file mode 100644 index 0000000..85a6819 --- /dev/null +++ b/tuned/plugins/plugin_net.py @@ -0,0 +1,62 @@ +import tuned.plugins +import tuned.logs +import tuned.monitors +from tuned.utils.nettool import ethcard +import os +import struct +import copy + +log = tuned.logs.get() + +class NetTuningPlugin(tuned.plugins.Plugin): + def __init__(self, devices, options): + """ + """ + super(self.__class__, self).__init__(None, options) + + self.devidle = {} + log.info("Devices: %s" % str(devices)); + self._load_monitor = tuned.monitors.get_repository().create("net", devices) + + @classmethod + def tunable_devices(cls): + available = [] + for root, dirs, files in os.walk("/sys/devices"): + if root.endswith("/net") and not root.endswith("/virtual/net"): + available += dirs + log.info("Tunable devices: %s" % str(available)) + return available + + def _updateIdle(self, dev, devload): + idle = self.devidle.setdefault(dev, {}) + idle.setdefault("LEVEL", 0) + for _type in ("READ", "WRITE"): + if devload[_type] <= 0.05: + idle.setdefault(_type, 0) + idle[_type] += 1 + else: + idle.setdefault(_type, 0) + idle[_type] = 0 + + def cleanup(self): + log.info("Cleanup") + + for dev in self.devidle.keys(): + if self.devidle[dev]["LEVEL"] > 0: + ethcard(dev).set_max_speed() + + def update_tuning(self): + load = self._load_monitor.get_load() + for dev, devload in load.iteritems(): + self._updateIdle(dev, devload) + print self.devidle + if self.devidle[dev]["LEVEL"] == 0 and self.devidle[dev]["READ"] >= 6 and self.devidle[dev]["WRITE"] >= 6: + self.devidle[dev]["LEVEL"] = 1 + + log.info("%s: setting 100Mbps" % dev) + ethcard(dev).set_speed(100) + if self.devidle[dev]["LEVEL"] > 0 and (self.devidle[dev]["READ"] == 0 or self.devidle[dev]["WRITE"] == 0): + self.devidle[dev]["LEVEL"] = 0 + + log.info("%s: setting maximal speed" % dev) + ethcard(dev).set_max_speed() diff --git a/tuned/utils/nettool.py b/tuned/utils/nettool.py new file mode 100644 index 0000000..676efca --- /dev/null +++ b/tuned/utils/nettool.py @@ -0,0 +1,210 @@ +__all__ = ["ethcard"] + +import tuned.logs +from subprocess import * +import re + +log = tuned.logs.get() + +class Nettool: + + _advertise_values = { # [ half, full ] + 10 : [ 0x001, 0x002 ], + 100 : [ 0x004, 0x008 ], + 1000 : [ 0x010, 0x020 ], + 2500 : [ 0, 0x8000 ], + 10000 : [ 0, 0x1000 ], + "auto" : 0x03F + } + + _disabled = False + + def __init__(self, interface): + self._interface = interface; + self.update() + + log.debug("%s: speed %s, full duplex %s, autoneg %s, link %s" % (interface, self.speed, self.full_duplex, self.autoneg, self.link)) + log.debug("%s: supports: autoneg %s, modes %s" % (interface, self.supported_autoneg, self.supported_modes)) + log.debug("%s: advertises: autoneg %s, modes %s" % (interface, self.advertised_autoneg, self.advertised_modes)) + +# def __del__(self): +# if self.supported_autoneg: +# self._set_advertise(self._advertise_values["auto"]) + + def _clean_status(self): + self.speed = 0 + self.full_duplex = False + self.autoneg = False + self.link = False + + self.supported_modes = [] + self.supported_autoneg = False + + self.advertised_modes = [] + self.advertised_autoneg = False + + def _calculate_mode(self, modes): + mode = 0; + for m in modes: + mode += self._advertise_values[m[0]][ 1 if m[1] else 0 ] + + return mode + + def _set_autonegotiation(self, enable): + if self.autoneg == enable: + return True + + if not self.supported_autoneg: + return False + + return 0 == call(["ethtool", "-s", self._interface, "autoneg", "on" if enable else "off"]) + + def _set_advertise(self, value): + if not self._set_autonegotiation(True): + return False + + return 0 == call(["ethtool", "-s", self._interface, "advertise", "0x%03x" % value]) + + def get_max_speed(self): + max = 0 + for mode in self.supported_modes: + if mode[0] > max: max = mode[0] + + if max > 0: + return max + else: + return 1000 + + def set_max_speed(self): + if self._disabled or not self.supported_autoneg: + return False + + #if self._set_advertise(self._calculateMode(self.supported_modes)): + if self._set_advertise(self._advertise_values["auto"]): + self.update() + return True + else: + return False + + def set_speed(self, speed): + if self._disabled or not self.supported_autoneg: + return False + + mode = 0 + for am in self._advertise_values: + if am == "auto": continue + if am <= speed: + mode += self._advertise_values[am][0]; + mode += self._advertise_values[am][1]; + + effective_mode = mode & self._calculate_mode(self.supported_modes) + + log.debug("%s: set_speed(%d) - effective_mode 0x%03x" % (self._interface, speed, effective_mode)) + + if self._set_advertise(effective_mode): + self.update() + return True + else: + return False + + def update(self): + if self._disabled: + return + + # run ethtool and preprocess output + + p_ethtool = Popen(["ethtool", self._interface], stdout=PIPE, stderr=PIPE) + p_filter = Popen(["sed", "s/^\s*//;s/:\s*/:\\n/g"], stdin=p_ethtool.stdout, stdout=PIPE) + + output = p_filter.communicate()[0] + errors = p_ethtool.communicate()[1] + + if errors != "": + log.warning("%s: some errors were reported by 'ethtool'" % self._interface) + log.debug("%s: %s" % (self._interface, errors.replace("\n", r"\n"))) + self._clean_status() + self._disabled = True + return + + # parses output - kind of FSM + + self._clean_status() + + re_speed = re.compile(r"(\d+)") + re_mode = re.compile(r"(\d+)baseT/(Half|Full)") + + state = "wait" + + for line in output.split("\n"): + + if line.endswith(":"): + section = line[:-1] + if section == "Speed": state = "speed" + elif section == "Duplex": state = "duplex" + elif section == "Auto-negotiation": state = "autoneg" + elif section == "Link detected": state = "link" + elif section == "Supported link modes": state = "supported_modes" + elif section == "Supports auto-negotiation": state = "supported_autoneg" + elif section == "Advertised link modes": state = "advertised_modes" + elif section == "Advertised auto-negotiation": state = "advertised_autoneg" + else: state = "wait" + del section + + elif state == "speed": + # Try to determine speed. If it fails, assume 1gbit ethernet + try: + self.speed = re_speed.match(line).group(1) + except: + self.speed = 1000 + state = "wait" + + elif state == "duplex": + self.full_duplex = line == "Full" + state = "wait" + + elif state == "autoneg": + self.autoneg = (line == "yes" or line == "on") + state = "wait" + + elif state == "link": + self.link = line == "yes" + state = "wait" + + elif state == "supported_modes": + # Try to determine supported modes. If it fails, assume 1gibt ethernet fullduplex works + try: + for m in line.split(): + (s, d) = re_mode.match(m).group(1,2) + self.supported_modes.append( (int(s), d == "Full") ) + del m,s,d + except: + self.supported_modes.append((1000, True)) + + + elif state == "supported_autoneg": + self.supported_autoneg = line == "Yes" + state = "wait" + + elif state == "advertised_modes": + # Try to determine advertised modes. If it fails, assume 1gibt ethernet fullduplex works + try: + if line != "Not reported": + for m in line.split(): + (s, d) = re_mode.match(m).group(1,2) + self.advertised_modes.append( (int(s), d == "Full") ) + del m,s,d + except: + self.advertised_modes.append((1000, True)) + + elif state == "advertised_autoneg": + self.advertised_autoneg = line == "Yes" + state = "wait" + +def ethcard(interface): + if not interface in ethcard.list: + ethcard.list[interface] = Nettool(interface) + + return ethcard.list[interface] + +ethcard.list = {} +