From: Mark Huang Date: Mon, 24 Apr 2006 20:04:13 +0000 (+0000) Subject: - move all function documentation inside their respective functions X-Git-Tag: after-util-vserver-0_30_208-revert~25 X-Git-Url: http://git.onelab.eu/?p=util-vserver.git;a=commitdiff_plain;h=5d96a99f6b8514fbfd6fdc585ba7cdba96873acd - move all function documentation inside their respective functions - format_tc_rate: optimize formatting for whole gbit and mbit values - get_slice: split into two functions, get_slice() and get_xid() - support setting exempt minrate/maxrate - support getting exempt minrate/maxrate and current byte counts - add set() function, same as on() but just pushes to the default netdevice - remove deprecated setcap() and getcap() functions; rewrote pl_mom bandwidth monitor to use this module, so we can now finally kill resman --- diff --git a/python/bwlimit.py b/python/bwlimit.py index fbe825f..5f35fe8 100644 --- a/python/bwlimit.py +++ b/python/bwlimit.py @@ -46,7 +46,7 @@ # Mark Huang # Copyright (C) 2006 The Trustees of Princeton University # -# $Id: bwlimit.py,v 1.10 2006/03/14 22:57:50 smuir Exp $ +# $Id: bwlimit.py,v 1.11 2006/03/15 16:41:21 smuir Exp $ # import sys, os, re, getopt @@ -175,8 +175,11 @@ suffixes = { } -# Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second def get_tc_rate(s): + """ + Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second + """ + if type(s) == int: return s m = re.match(r"([0-9.]+)(\D*)", s) @@ -189,9 +192,14 @@ def get_tc_rate(s): return -1 -# Prints a tc rate string def format_tc_rate(rate): - if rate >= 1000000: + """ + Formats a bits/second rate into a tc rate string + """ + + if rate >= 1000000000 and (rate % 1000000000) == 0: + return "%.0fgbit" % (rate / 1000000000.) + elif rate >= 1000000 and (rate % 1000000) == 0: return "%.0fmbit" % (rate / 1000000.) elif rate >= 1000: return "%.0fkbit" % (rate / 1000.) @@ -214,8 +222,10 @@ def read_bwcap(bwcap_file): return bwcap -# Get current (live) value of bwcap def get_bwcap(dev = dev): + """ + Get the current (live) value of the node bandwidth cap + """ state = tc("-d class show dev %s" % dev) base_re = re.compile(r"class htb 1:10 parent 1:1 .*ceil ([^ ]+) .*") @@ -227,34 +237,47 @@ def get_bwcap(dev = dev): return get_tc_rate(base_classes[0].group(1)) -# Get slice xid (500) from slice name ("500" or "princeton_mlh") or -# slice name ("princeton_mlh") from slice xid (500). -def get_slice(xid_or_name): +def get_slice(xid): + """ + Get slice name ("princeton_mlh") from slice xid (500) + """ - if xid_or_name == root_xid: + if xid == root_xid: return "root" - if xid_or_name == default_xid: + if xid == default_xid: return "default" - if isinstance(xid_or_name, (int, long)): - try: - return pwd.getpwuid(xid_or_name).pw_name - except KeyError: - pass - else: + try: + return pwd.getpwuid(xid).pw_name + except KeyError: + pass + + return None + +def get_xid(slice): + """ + Get slice xid ("princeton_mlh") from slice name ("500" or "princeton_mlh") + """ + + if slice == "root": + return root_xid + if slice == "default": + return default_xid + try: try: - try: - return int(xid_or_name) - except ValueError: - pass - return pwd.getpwnam(xid_or_name).pw_uid - except KeyError: + return int(slice) + except ValueError: pass + return pwd.getpwnam(slice).pw_uid + except KeyError: + pass return None - -# Shortcut for running a command def run(cmd, input = None): + """ + Shortcut for running a shell command + """ + try: if verbose: sys.stderr.write("Executing: " + cmd + "\n") @@ -272,13 +295,18 @@ def run(cmd, input = None): return None -# Shortcut for running a tc command def tc(cmd): + """ + Shortcut for running a tc command + """ + return run(TC + " " + cmd) -# (Re)initialize the bandwidth limits on this node def init(dev, bwcap): + """ + (Re)initialize the bandwidth limits on this node + """ # load the module used to manage exempt classes run("/sbin/modprobe ip_set_iphash") @@ -325,57 +353,112 @@ def init(dev, bwcap): on(default_xid, dev, share = default_share) -# Get the bandwidth limits for a particular slice xid as a tuple (xid, -# share, minrate, maxrate), or all classes as a list of tuples. def get(xid = None, dev = dev): + """ + Get the bandwidth limits and current byte totals for a + particular slice xid as a tuple (xid, share, minrate, maxrate, + minexemptrate, maxexemptrate, bytes, exemptbytes), or all classes + as a list of such tuples. + """ + if xid is None: ret = [] else: ret = None - # class htb 1:1002 parent 1:10 leaf 81b3: prio 1 rate 8bit ceil 5000Kbit burst 1600b cburst 4Kb - for line in tc("-d class show dev %s" % dev): - # Search for child classes of 1:10 - m = re.match(r"class htb 1:([0-9a-f]+) parent 1:10", line) - if m is None: - continue - - # If we are looking for a particular class - classid = int(m.group(1), 16) & default_xid - if xid is not None and xid != classid: - continue - - # Parse share - share = 1 - m = re.search(r"quantum (\d+)", line) - if m is not None: - share = int(m.group(1)) / quantum - - # Parse minrate - minrate = bwmin - m = re.search(r"rate (\w+)", line) - if m is not None: - minrate = get_tc_rate(m.group(1)) + rates = {} + rate = None + + # ... + # class htb 1:1000 parent 1:10 leaf 1000: prio 0 quantum 8000 rate 8bit ceil 10000Kbit ... + # Sent 6851486 bytes 49244 pkt (dropped 0, overlimits 0 requeues 0) + # ... + # class htb 1:2000 parent 1:20 leaf 2000: prio 0 quantum 8000 rate 8bit ceil 1000Mbit ... + # Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) + # ... + for line in tc("-s -d class show dev %s" % dev): + # Rate parameter line + params = re.match(r"class htb 1:([0-9a-f]+) parent 1:(10|20)", line) + # Statistics line + stats = re.match(r".* Sent ([0-9]+) bytes", line) + # Another class + ignore = re.match(r"class htb", line) + + if params is not None: + # Which class + if params.group(2) == "10": + min = 'min' + max = 'max' + bytes = 'bytes' + else: + min = 'minexempt' + max = 'maxexempt' + bytes = 'exemptbytes' - # Parse maxrate - maxrate = bwmax - m = re.search(r"ceil (\w+)", line) - if m is not None: - maxrate = get_tc_rate(m.group(1)) + # Slice ID + id = int(params.group(1), 16) & 0x0FFF; - if xid is None: - # Return a list of parameters - ret.append((classid, share, minrate, maxrate)) - else: - # Return the parameters for this class - ret = (classid, share, minrate, maxrate) - break + if rates.has_key(id): + rate = rates[id] + else: + rate = {'id': id} + + # Parse share + rate['share'] = 1 + m = re.search(r"quantum (\d+)", line) + if m is not None: + rate['share'] = int(m.group(1)) / quantum + + # Parse minrate + rate[min] = bwmin + m = re.search(r"rate (\w+)", line) + if m is not None: + rate[min] = get_tc_rate(m.group(1)) + + # Parse maxrate + rate[max] = bwmax + m = re.search(r"ceil (\w+)", line) + if m is not None: + rate[max] = get_tc_rate(m.group(1)) + + # Which statistics to parse + rate['stats'] = bytes + + rates[id] = rate + + elif stats is not None: + if rate is not None: + rate[rate['stats']] = int(stats.group(1)) + + elif ignore is not None: + rate = None + + # Keep parsing until we get everything + if rate is not None and \ + rate.has_key('min') and rate.has_key('minexempt') and \ + rate.has_key('max') and rate.has_key('maxexempt') and \ + rate.has_key('bytes') and rate.has_key('exemptbytes'): + params = (rate['id'], rate['share'], + rate['min'], rate['max'], + rate['minexempt'], rate['maxexempt'], + rate['bytes'], rate['exemptbytes']) + if xid is None: + # Return a list of parameters + ret.append(params) + rate = None + elif xid == rate['id']: + # Return the parameters for this class + ret = params + break return ret -# Apply specified bandwidth limit to the specified slice xid -def on(xid, dev = dev, share = None, minrate = None, maxrate = None): +def on(xid, dev = dev, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None): + """ + Apply specified bandwidth limit to the specified slice xid + """ + # Get defaults from current state if available cap = get(xid, dev) if cap is not None: @@ -385,15 +468,13 @@ def on(xid, dev = dev, share = None, minrate = None, maxrate = None): minrate = cap[2] if maxrate is None: maxrate = cap[3] + if minexemptrate is None: + minexemptrate = cap[4] + if maxexemptrate is None: + maxexemptrate = cap[5] # Figure out what the current node bandwidth cap is - bwcap = bwmax - for line in tc("-d class show dev %s" % dev): - # Search for 1:10 - m = re.match(r"class htb 1:10.*ceil (\w+)", line) - if m is not None: - bwcap = get_tc_rate(m.group(1)) - break + bwcap = get_bwcap() # Set defaults if share is None: @@ -406,19 +487,31 @@ def on(xid, dev = dev, share = None, minrate = None, maxrate = None): maxrate = bwcap else: maxrate = get_tc_rate(maxrate) + if minexemptrate is None: + minexemptrate = minrate + else: + minexemptrate = get_tc_rate(minexemptrate) + if maxexemptrate is None: + maxexemptrate = bwmax + else: + maxexemptrate = get_tc_rate(maxexemptrate) # Sanity checks if maxrate > bwcap: maxrate = bwcap if minrate > maxrate: minrate = maxrate + if maxexemptrate > bwmax: + maxexemptrate = bwmax + if minexemptrate > maxexemptrate: + minexemptrate = maxexemptrate # Set up subclasses for the slice tc("class replace dev %s parent 1:10 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \ (dev, default_minor | xid, minrate, maxrate, share * quantum)) tc("class replace dev %s parent 1:20 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \ - (dev, exempt_minor | xid, minrate, bwmax, share * quantum)) + (dev, exempt_minor | xid, minexemptrate, maxexemptrate, share * quantum)) # Attach a FIFO to each subclass, which helps to throttle back # processes that are sending faster than the token buckets can @@ -430,10 +523,22 @@ def on(xid, dev = dev, share = None, minrate = None, maxrate = None): (dev, exempt_minor | xid, exempt_minor | xid)) +def set(xid, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None): + on(xid = xid, share = share, + minrate = minrate, maxrate = maxrate, + minexemptrate = minexemptrate, maxexemptrate = maxexemptrate) + + # Remove class associated with specified slice xid. If further packets # are seen from this slice, they will be classified into the default # class 1:1FFF. def off(xid, dev = dev): + """ + Remove class associated with specified slice xid. If further + packets are seen from this slice, they will be classified into the + default class 1:1FFF. + """ + cap = get(xid, dev) if cap is not None: tc("class del dev %s classid 1:%x" % (dev, default_minor | xid)) @@ -441,6 +546,10 @@ def off(xid, dev = dev): def exempt_init(group_name, node_ips): + """ + Initialize the list of destinations exempt from the node bandwidth + (burst) cap. + """ # Clean up iptables = "/sbin/iptables -t vnet %s POSTROUTING" @@ -472,23 +581,21 @@ Options: -d device Network interface (default: %s) -r rate Node bandwidth cap (default: %s) -q quantum Share multiplier (default: %d bytes) + -n Print rates in numeric bits per second + -v Enable verbose debug messages -h This message Commands: init - (Re)initialize bandwidth caps. - on slice [share] [minrate] [maxrate] - Set bandwidth cap for the specified slice + (Re)initialize all bandwidth parameters + on slice [share|-] [minrate|-] [maxrate|-] [minexemptrate|-] [maxexemptrate|-] + Set bandwidth parameter(s) for the specified slice off slice - Remove bandwidth caps for the specified slice + Remove all bandwidth parameters for the specified slice get - Get all bandwidth caps + Get all bandwidth parameters for all slices get slice - Get bandwidth caps for the specified slice - getcap slice - Get maxrate for the specified slice - setcap slice maxrate - Set maxrate for the specified slice + Get bandwidth parameters for the specified slice """ % (sys.argv[0], dev, bwcap_description, quantum) sys.exit(1) @@ -497,12 +604,15 @@ def main(): global dev, quantum, verbose # Defaults + numeric = False bwcap = get_bwcap() - (opts, argv) = getopt.getopt(sys.argv[1:], "f:d:r:g:q:vh") + (opts, argv) = getopt.getopt(sys.argv[1:], "d:nr:q:vh") for (opt, optval) in opts: if opt == '-d': dev = optval + elif opt == '-n': + numeric = True elif opt == '-r': bwcap = get_tc_rate(optval) elif opt == '-q': @@ -521,62 +631,66 @@ def main(): # Show if len(argv) >= 2: # Show a particular slice - xid = get_slice(argv[1]) + xid = get_xid(argv[1]) if xid is None: sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1]) usage() - caps = [get(xid, dev)] + params = get(xid, dev) + if params is None: + paramslist = [] + else: + paramslist = [params] else: # Show all slices - caps = get(None, dev) + paramslist = get(None, dev) - for (xid, share, minrate, maxrate) in caps: + for (xid, share, + minrate, maxrate, + minexemptrate, maxexemptrate, + bytes, exemptbytes) in paramslist: slice = get_slice(xid) if slice is None: # Orphaned (not associated with a slice) class slice = "%d?" % xid - print "%s %d %s %s" % \ - (slice, share, format_tc_rate(minrate), format_tc_rate(maxrate)) + if numeric: + print "%s %d %d %d %d %d %d %d" % \ + (slice, share, + minrate, maxrate, + minexemptrate, maxexemptrate, + bytes, exemptbytes) + else: + print "%s %d %s %s %s %s %d %d" % \ + (slice, share, + format_tc_rate(minrate), format_tc_rate(maxrate), + format_tc_rate(minexemptrate), format_tc_rate(maxexemptrate), + bytes, exemptbytes) elif len(argv) >= 2: # slice, ... - xid = get_slice(argv[1]) + xid = get_xid(argv[1]) if xid is None: sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1]) usage() - if argv[0] == "on" or argv[0] == "add" or argv[0] == "replace": + if argv[0] == "on" or argv[0] == "add" or argv[0] == "replace" or argv[0] == "set": # Enable cap args = [] if len(argv) >= 3: - # ... share, minrate, maxrate - casts = [int, get_tc_rate, get_tc_rate] + # ... share, minrate, maxrate, minexemptrate, maxexemptrate + casts = [int, get_tc_rate, get_tc_rate, get_tc_rate, get_tc_rate] for i, arg in enumerate(argv[2:]): if i >= len(casts): break - args.append(casts[i](arg)) + if arg == "-": + args.append(None) + else: + args.append(casts[i](arg)) on(xid, dev, *args) elif argv[0] == "off" or argv[0] == "del": # Disable cap off(xid, dev) - # Backward compatibility with old resman script - elif argv[0] == "getcap": - # Get maxrate - cap = get(xid, dev) - if cap is not None: - (xid, share, minrate, maxrate) = cap - print format_tc_rate(maxrate) - - # Backward compatibility with old resman script - elif argv[0] == "setcap": - if len(argv) >= 3: - # Set maxrate - on(xid, dev, maxrate = get_tc_rate(argv[2])) - else: - usage() - else: usage()