More convenient output of show.
[util-vserver-pl.git] / python / bwlimit.py
1 #!/usr/bin/python
2
3 # Bandwidth limit module for PlanetLab nodes. The intent is to use the
4 # Hierarchical Token Bucket (HTB) queueing discipline (qdisc) to allow
5 # slices to fairly share access to available node bandwidth. We
6 # currently define three classes of "available node bandwidth":
7 #
8 # 1. Available hardware bandwidth (bwmax): The maximum rate of the
9 # hardware.
10 #
11 # 2. Available capped bandwidth (bwcap): The maximum rate allowed to
12 # non-exempt destinations. By default, equal to bwmax, but may be
13 # lowered by PIs.
14 #
15 # 3. Available uncapped ("exempt") bandwidth: The difference between
16 # bwmax and what is currently being used of bwcap, or the maximum rate
17 # allowed to destinations exempt from caps (e.g., Internet2).
18 #
19 # All three classes of bandwidth are fairly shared according to the
20 # notion of "shares". For instance, if the node is capped at 5 Mbps,
21 # there are N slices, and each slice has 1 share, then each slice
22 # should get at least 5/N Mbps of bandwidth. How HTB is implemented
23 # makes this statement a little too simplistic. What it really means
24 # is that during any single time period, only a certain number of
25 # bytes can be sent onto the wire. Each slice is guaranteed that at
26 # least some small number of its bytes will be sent. Whatever is left
27 # over from the budget, is split in proportion to the number of shares
28 # each slice has.
29 #
30 # Even if the node is not capped at a particular limit (bwcap ==
31 # bwmax), this module enforces fair share access to bwmax. Also, if
32 # the node is capped at a particular limit, rules may optionally be
33 # defined that classify certain packets into the "exempt" class. This
34 # class receives whatever bandwidth is leftover between bwcap and
35 # bwmax; slices fairly share this bandwidth as well.
36 #
37 # The root context is exempt from sharing and can send as much as it
38 # needs to.
39 #
40 # Some relevant URLs:
41 #
42 # 1. http://lartc.org/howto               for how to use tc
43 # 2. http://luxik.cdi.cz/~devik/qos/htb/  for info on HTB
44 #
45 # Andy Bavier <acb@cs.princeton.edu>
46 # Mark Huang <mlhuang@cs.princeton.edu>
47 # Copyright (C) 2006 The Trustees of Princeton University
48 #
49 # $Id: bwlimit.py,v 1.15 2007/02/07 04:21:11 mlhuang Exp $
50 #
51
52 import sys, os, re, getopt
53 from sets import Set
54 import pwd
55
56
57 # Where the tc binary lives
58 TC = "/sbin/tc"
59
60 # Default interface
61 dev = "eth0"
62
63 # Verbosity level
64 verbose = 0
65
66 # bwmin should be small enough that it can be considered negligibly
67 # slow compared to the hardware. 8 bits/second appears to be the
68 # smallest value supported by tc.
69 bwmin = 1000
70
71 # bwmax should be large enough that it can be considered at least as
72 # fast as the hardware.
73 bwmax = 1000*1000*1000
74
75 # quantum is the maximum number of bytes that can be borrowed by a
76 # share (or slice, if each slice gets 1 share) in one time period
77 # (with HZ=1000, 1 ms). If multiple slices are competing for bandwidth
78 # above their guarantees, and each is attempting to borrow up to the
79 # node bandwidth cap, quantums control how the excess bandwidth is
80 # distributed. Slices with 2 shares will borrow twice the amount in
81 # one time period as slices with 1 share, so averaged over time, they
82 # will get twice as much of the excess bandwidth. The value should be
83 # as small as possible and at least 1 MTU. By default, it would be
84 # calculated as bwmin/10, but since we use such small a value for
85 # bwmin, it's better to just set it to a value safely above 1 Ethernet
86 # MTU.
87 quantum = 1600
88
89 # cburst is the maximum number of bytes that can be burst onto the
90 # wire in one time period (with HZ=1000, 1 ms). If multiple slices
91 # have data queued for transmission, cbursts control how long each
92 # slice can have the wire for. If not specified, it is set to the
93 # smallest possible value that would enable the slice's "ceil" rate
94 # (usually the node bandwidth cap), to be reached if a slice was able
95 # to borrow enough bandwidth to do so. For now, it's unclear how or if
96 # to relate this to the notion of shares, so just let tc set the
97 # default.
98 cburst = None
99
100 # There is another parameter that controls how bandwidth is allocated
101 # between slices on nodes that is outside the scope of HTB. We enforce
102 # a 16 GByte/day total limit on each slice, which works out to about
103 # 1.5mbit. If a slice exceeds this byte limit before the day finishes,
104 # it is capped at (i.e., its "ceil" rate is set to) the smaller of the
105 # node bandwidth cap or 1.5mbit. pl_mom is in charge of enforcing this
106 # rule and executes this script to override "ceil".
107
108 # We support multiple bandwidth limits, by reserving the top nibble of
109 # the minor classid to be the "subclassid". Theoretically, we could
110 # support up to 15 subclasses, but for now, we only define two: the
111 # "default" subclass 1:10 that is capped at the node bandwidth cap (in
112 # this example, 5mbit) and the "exempt" subclass 1:20 that is capped
113 # at bwmax (i.e., not capped). The 1:1 parent class exists only to
114 # make the borrowing model work. All bandwidth above minimum
115 # guarantees is fairly shared (in this example, slice 2 is guaranteed
116 # at least 1mbit in addition to fair access to the rest), subject to
117 # the restrictions of the class hierarchy: namely, that the total
118 # bandwidth to non-exempt destinations should not exceed the node
119 # bandwidth cap.
120 #
121 #                         1:
122 #                         |
123 #                    1:1 (1gbit)
124 #           ______________|_____________
125 #          |                            |
126 #   1:10 (8bit, 5mbit)           1:20 (8bit, 1gbit)
127 #          |                            |
128 # 1:1000 (8bit, 5mbit),        1:2000 (8bit, 1gbit),
129 # 1:1001 (8bit, 5mbit),        1:2001 (8bit, 1gbit),
130 # 1:1002 (1mbit, 5mbit),       1:2002 (1mbit, 1gbit),
131 # ...                          ...
132 # 1:1FFF (8bit, 5mbit)         1:2FFF (8bit, 1gbit)
133 #
134 default_minor = 0x1000
135 exempt_minor = 0x2000
136
137 # root_xid is for the root context. The root context is exempt from
138 # fair sharing in both the default and exempt subclasses. The root
139 # context gets 5 shares by default.
140 root_xid = 0x0000
141 root_share = 5
142
143 # default_xid is for unclassifiable packets. Packets should not be
144 # classified here very often. They can be if a slice's HTB classes are
145 # deleted before its processes are. Each slice gets 1 share by
146 # default.
147 default_xid = 0x0FFF
148 default_share = 1
149
150 # See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
151 # warned that older versions of tc interpret "kbps", "mbps", "mbit",
152 # and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
153 # "kibit" and that if an older version is installed, all rates will
154 # be off by a small fraction.
155 suffixes = {
156     "":         1,
157     "bit":      1,
158     "kibit":    1024,
159     "kbit":     1000,
160     "mibit":    1024*1024,
161     "mbit":     1000000,
162     "gibit":    1024*1024*1024,
163     "gbit":     1000000000,
164     "tibit":    1024*1024*1024*1024,
165     "tbit":     1000000000000,
166     "bps":      8,
167     "kibps":    8*1024,
168     "kbps":     8000,
169     "mibps":    8*1024*1024,
170     "mbps":     8000000,
171     "gibps":    8*1024*1024*1024,
172     "gbps":     8000000000,
173     "tibps":    8*1024*1024*1024*1024,
174     "tbps":     8000000000000
175 }
176
177
178 def get_tc_rate(s):
179     """
180     Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
181     """
182
183     if type(s) == int:
184         return s
185     m = re.match(r"([0-9.]+)(\D*)", s)
186     if m is None:
187         return -1
188     suffix = m.group(2).lower()
189     if suffixes.has_key(suffix):
190         return int(float(m.group(1)) * suffixes[suffix])
191     else:
192         return -1
193
194 def format_bytes(bytes, si = True):
195     """
196     Formats bytes into a string
197     """
198     if si:
199         kilo = 1000.
200     else:
201         # Officially, a kibibyte
202         kilo = 1024.
203
204     if bytes >= (kilo * kilo * kilo):
205         return "%.1f GB" % (bytes / (kilo * kilo * kilo))
206     elif bytes >= 1000000:
207         return "%.1f MB" % (bytes / (kilo * kilo))
208     elif bytes >= 1000:
209         return "%.1f KB" % (bytes / kilo)
210     else:
211         return "%.0f bytes" % bytes
212
213 def format_tc_rate(rate):
214     """
215     Formats a bits/second rate into a tc rate string
216     """
217
218     if rate >= 1000000000 and (rate % 1000000000) == 0:
219         return "%.0fgbit" % (rate / 1000000000.)
220     elif rate >= 1000000 and (rate % 1000000) == 0:
221         return "%.0fmbit" % (rate / 1000000.)
222     elif rate >= 1000:
223         return "%.0fkbit" % (rate / 1000.)
224     else:
225         return "%.0fbit" % rate
226
227
228 # Parse /etc/planetlab/bwcap (or equivalent)
229 def read_bwcap(bwcap_file):
230     bwcap = bwmax
231     try:
232         fp = open(bwcap_file, "r")
233         line = fp.readline().strip()
234         if line:
235             bwcap = get_tc_rate(line)
236     except:
237         pass
238     if bwcap == -1:
239         bwcap = bwmax
240     return bwcap
241
242
243 def get_bwcap(dev = dev):
244     """
245     Get the current (live) value of the node bandwidth cap
246     """
247
248     state = tc("-d class show dev %s" % dev)
249     base_re = re.compile(r"class htb 1:10 parent 1:1 .*ceil ([^ ]+) .*")
250     base_classes = filter(None, map(base_re.match, state))
251     if not base_classes:
252         return -1
253     if len(base_classes) > 1:
254         raise Exception, "unable to get current bwcap"
255     return get_tc_rate(base_classes[0].group(1))
256
257
258 def get_slice(xid):
259     """
260     Get slice name ("princeton_mlh") from slice xid (500)
261     """
262
263     if xid == root_xid:
264         return "root"
265     if xid == default_xid:
266         return "default"
267     try:
268         return pwd.getpwuid(xid).pw_name
269     except KeyError:
270         pass
271
272     return None
273
274 def get_xid(slice):
275     """
276     Get slice xid ("princeton_mlh") from slice name ("500" or "princeton_mlh")
277     """
278
279     if slice == "root":
280         return root_xid
281     if slice == "default":
282         return default_xid
283     try:
284         try:
285             return int(slice)
286         except ValueError:
287             pass
288         return pwd.getpwnam(slice).pw_uid
289     except KeyError:
290         pass
291
292     return None
293
294 def run(cmd, input = None):
295     """
296     Shortcut for running a shell command
297     """
298
299     try:
300         if verbose:
301             sys.stderr.write("Executing: " + cmd + "\n")
302         if input is None:
303             fileobj = os.popen(cmd, "r")
304             output = fileobj.readlines()
305         else:
306             fileobj = os.popen(cmd, "w")
307             fileobj.write(input)
308             output = None
309         if fileobj.close() is None:
310             return output
311     except Exception, e:
312         pass
313     return None
314
315
316 def tc(cmd):
317     """
318     Shortcut for running a tc command
319     """
320
321     return run(TC + " " + cmd)
322
323
324 def init(dev = dev, bwcap = bwmax):
325     """
326     (Re)initialize the bandwidth limits on this node
327     """
328
329     # Load the module used to manage exempt classes
330     run("/sbin/modprobe ip_set_iphash")
331
332     # Save current settings
333     paramslist = get(None, dev)
334
335     # Delete root qdisc 1: if it exists. This will also automatically
336     # delete any child classes.
337     for line in tc("qdisc show dev %s" % dev):
338         # Search for the root qdisc 1:
339         m = re.match(r"qdisc htb 1:", line)
340         if m is not None:
341             tc("qdisc del dev %s root handle 1:" % dev)
342             break
343
344     # Initialize HTB. The "default" clause specifies that if a packet
345     # fails classification, it should go into the class with handle
346     # 1FFF.
347     tc("qdisc add dev %s root handle 1: htb default %x" % \
348        (dev, default_minor | default_xid))
349
350     # Set up a parent class from which all subclasses borrow.
351     tc("class add dev %s parent 1: classid 1:1 htb rate %dbit" % \
352        (dev, bwmax))
353
354     # Set up a subclass that represents the node bandwidth cap. We
355     # allow each slice to borrow up to this rate, so it is also
356     # usually the "ceil" rate for each slice.
357     tc("class add dev %s parent 1:1 classid 1:10 htb rate %dbit ceil %dbit" % \
358        (dev, bwmin, bwcap))
359
360     # Set up a subclass that represents "exemption" from the node
361     # bandwidth cap. Once the node bandwidth cap is reached, bandwidth
362     # to exempt destinations can still be fairly shared up to bwmax.
363     tc("class add dev %s parent 1:1 classid 1:20 htb rate %dbit ceil %dbit" % \
364        (dev, bwmin, bwmax))
365
366     # Set up the root class (and tell VNET what it is). Packets sent
367     # by root end up here and are capped at the node bandwidth
368     # cap.
369     #on(root_xid, dev, share = root_share)
370     #try:
371     #    file("/proc/sys/vnet/root_class", "w").write("%d" % ((1 << 16) | default_minor | root_xid))
372     #except:
373     #    pass
374
375     # Set up the default class. Packets that fail classification end
376     # up here.
377     on(default_xid, dev, share = default_share)
378
379     # Restore old settings
380     for (xid, share,
381          minrate, maxrate,
382          minexemptrate, maxexemptrate,
383          bytes, exemptbytes) in paramslist:
384         if xid not in (root_xid, default_xid):
385             on(xid, dev, share, minrate, maxrate, minexemptrate, maxexemptrate)
386
387
388 def get(xid = None, dev = dev):
389     """
390     Get the bandwidth limits and current byte totals for a
391     particular slice xid as a tuple (xid, share, minrate, maxrate,
392     minexemptrate, maxexemptrate, bytes, exemptbytes), or all classes
393     as a list of such tuples.
394     """
395
396     if xid is None:
397         ret = []
398     else:
399         ret = None
400
401     rates = {}
402     rate = None
403
404     # ...
405     # class htb 1:1000 parent 1:10 leaf 1000: prio 0 quantum 8000 rate 8bit ceil 10000Kbit ...
406     #  Sent 6851486 bytes 49244 pkt (dropped 0, overlimits 0 requeues 0)
407     # ...
408     # class htb 1:2000 parent 1:20 leaf 2000: prio 0 quantum 8000 rate 8bit ceil 1000Mbit ...
409     #  Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) 
410     # ...
411     for line in tc("-s -d class show dev %s" % dev):
412         # Rate parameter line
413         params = re.match(r"class htb 1:([0-9a-f]+) parent 1:(10|20)", line)
414         # Statistics line
415         stats = re.match(r".* Sent ([0-9]+) bytes", line)
416         # Another class
417         ignore = re.match(r"class htb", line)
418
419         if params is not None:
420             # Which class
421             if params.group(2) == "10":
422                 min = 'min'
423                 max = 'max'
424                 bytes = 'bytes'
425             else:
426                 min = 'minexempt'
427                 max = 'maxexempt'
428                 bytes = 'exemptbytes'
429
430             # Slice ID
431             id = int(params.group(1), 16) & 0x0FFF;
432
433             if rates.has_key(id):
434                 rate = rates[id]
435             else:
436                 rate = {'id': id}
437
438             # Parse share
439             rate['share'] = 1
440             m = re.search(r"quantum (\d+)", line)
441             if m is not None:
442                 rate['share'] = int(m.group(1)) / quantum
443
444             # Parse minrate
445             rate[min] = bwmin
446             m = re.search(r"rate (\w+)", line)
447             if m is not None:
448                 rate[min] = get_tc_rate(m.group(1))
449
450             # Parse maxrate
451             rate[max] = bwmax
452             m = re.search(r"ceil (\w+)", line)
453             if m is not None:
454                 rate[max] = get_tc_rate(m.group(1))
455
456             # Which statistics to parse
457             rate['stats'] = bytes
458
459             rates[id] = rate
460
461         elif stats is not None:
462             if rate is not None:
463                 rate[rate['stats']] = int(stats.group(1))
464
465         elif ignore is not None:
466             rate = None
467
468         # Keep parsing until we get everything
469         if rate is not None and \
470            rate.has_key('min') and rate.has_key('minexempt') and \
471            rate.has_key('max') and rate.has_key('maxexempt') and \
472            rate.has_key('bytes') and rate.has_key('exemptbytes'):
473             params = (rate['id'], rate['share'],
474                       rate['min'], rate['max'],
475                       rate['minexempt'], rate['maxexempt'],
476                       rate['bytes'], rate['exemptbytes'])
477             if xid is None:
478                 # Return a list of parameters
479                 ret.append(params)
480                 rate = None
481             elif xid == rate['id']:
482                 # Return the parameters for this class
483                 ret = params
484                 break
485
486     return ret
487
488
489 def on(xid, dev = dev, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None):
490     """
491     Apply specified bandwidth limit to the specified slice xid
492     """
493
494     # Get defaults from current state if available
495     cap = get(xid, dev)
496     if cap is not None:
497         if share is None:
498             share = cap[1]
499         if minrate is None:
500             minrate = cap[2]
501         if maxrate is None:
502             maxrate = cap[3]
503         if minexemptrate is None:
504             minexemptrate = cap[4]
505         if maxexemptrate is None:
506             maxexemptrate = cap[5]
507
508     # Figure out what the current node bandwidth cap is
509     bwcap = get_bwcap()
510
511     # Set defaults
512     if share is None:
513         share = default_share
514     if minrate is None:
515         minrate = bwmin
516     else:
517         minrate = get_tc_rate(minrate)
518     if maxrate is None:
519         maxrate = bwcap
520     else:
521         maxrate = get_tc_rate(maxrate)
522     if minexemptrate is None:
523         minexemptrate = minrate
524     else:
525         minexemptrate = get_tc_rate(minexemptrate)
526     if maxexemptrate is None:
527         maxexemptrate = bwmax
528     else:
529         maxexemptrate = get_tc_rate(maxexemptrate)
530
531     # Sanity checks
532     if maxrate < bwmin:
533         maxrate = bwmin
534     if maxrate > bwcap:
535         maxrate = bwcap
536     if minrate < bwmin:
537         minrate = bwmin
538     if minrate > maxrate:
539         minrate = maxrate
540     if maxexemptrate < bwmin:
541         maxexemptrate = bwmin
542     if maxexemptrate > bwmax:
543         maxexemptrate = bwmax
544     if minexemptrate < bwmin:
545         minexemptrate = bwmin
546     if minexemptrate > maxexemptrate:
547         minexemptrate = maxexemptrate
548
549     # Set up subclasses for the slice
550     tc("class replace dev %s parent 1:10 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
551        (dev, default_minor | xid, minrate, maxrate, share * quantum))
552
553     tc("class replace dev %s parent 1:20 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
554        (dev, exempt_minor | xid, minexemptrate, maxexemptrate, share * quantum))
555
556     # Attach a FIFO to each subclass, which helps to throttle back
557     # processes that are sending faster than the token buckets can
558     # support.
559     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
560        (dev, default_minor | xid, default_minor | xid))
561
562     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
563        (dev, exempt_minor | xid, exempt_minor | xid))
564
565
566 def set(xid, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None):
567     # Get defaults from current state if available
568     # get(xid,dev) = ( xid, share, minrate, maxrate, minexemptrate, maxexemptrate, bytes, i2bytes )
569     current = get(xid, dev)
570     if current == None: current = () # must be a new slice if returned nothing.
571     new = ( share, minrate, maxrate, minexemptrate, maxexemptrate )
572     if current[1:6] != new:
573         on(xid = xid, share = share,
574             minrate = minrate, maxrate = maxrate,
575             minexemptrate = minexemptrate, maxexemptrate = maxexemptrate)
576
577
578 # Remove class associated with specified slice xid. If further packets
579 # are seen from this slice, they will be classified into the default
580 # class 1:1FFF.
581 def off(xid, dev = dev):
582     """
583     Remove class associated with specified slice xid. If further
584     packets are seen from this slice, they will be classified into the
585     default class 1:1FFF.
586     """
587
588     cap = get(xid, dev)
589     if cap is not None:
590         tc("class del dev %s classid 1:%x" % (dev, default_minor | xid))
591         tc("class del dev %s classid 1:%x" % (dev, exempt_minor | xid))
592
593
594 def exempt_init(group_name, node_ips):
595     """
596     Initialize the list of destinations exempt from the node bandwidth
597     (burst) cap.
598     """
599
600     # Check of set exists
601     set = run("/sbin/ipset -S " + group_name)
602     if set == None:
603         # Create a hashed IP set of all of these destinations
604         lines = ["-N %s iphash" % group_name]
605         add_cmd = "-A %s " % group_name
606         lines += [(add_cmd + ip) for ip in node_ips]
607         lines += ["COMMIT"]
608         restore = "\n".join(lines) + "\n"
609         run("/sbin/ipset -R", restore)
610     else: # set exists
611         # Check all hosts and add missing.
612         for nodeip in node_ips:
613             if not run("/sbin/ipset -T %s %s" % (group_name, nodeip)):
614                run("/sbin/ipset -A %s %s" % (group_name, nodeip))
615
616
617 def usage():
618     bwcap_description = format_tc_rate(get_bwcap())
619         
620     print """
621 Usage:
622
623 %s [OPTION]... [COMMAND] [ARGUMENT]...
624
625 Options:
626         -d device       Network interface (default: %s)
627         -r rate         Node bandwidth cap (default: %s)
628         -q quantum      Share multiplier (default: %d bytes)
629         -n              Print rates in numeric bits per second
630         -v              Enable verbose debug messages
631         -h              This message
632
633 Commands:
634         init
635                 (Re)initialize all bandwidth parameters
636         on slice [share|-] [minrate|-] [maxrate|-] [minexemptrate|-] [maxexemptrate|-]
637                 Set bandwidth parameter(s) for the specified slice
638         off slice
639                 Remove all bandwidth parameters for the specified slice
640         get
641                 Get all bandwidth parameters for all slices
642         get slice
643                 Get bandwidth parameters for the specified slice
644 """ % (sys.argv[0], dev, bwcap_description, quantum)
645     sys.exit(1)
646     
647
648 def main():
649     global dev, quantum, verbose
650
651     # Defaults
652     numeric = False
653     bwcap = get_bwcap()
654
655     (opts, argv) = getopt.getopt(sys.argv[1:], "d:nr:q:vh")
656     for (opt, optval) in opts:
657         if opt == '-d':
658             dev = optval
659         elif opt == '-n':
660             numeric = True
661         elif opt == '-r':
662             bwcap = get_tc_rate(optval)
663         elif opt == '-q':
664             quantum = int(optval)
665         elif opt == '-v':
666             verbose += 1
667         elif opt == '-h':
668             usage()
669
670     if len(argv):
671         if argv[0] == "init" or (argv[0] == "on" and len(argv) == 1):
672             # (Re)initialize
673             init(dev, get_tc_rate(bwcap))
674
675         elif argv[0] == "get" or argv[0] == "show":
676             # Show
677             if len(argv) >= 2:
678                 # Show a particular slice
679                 xid = get_xid(argv[1])
680                 if xid is None:
681                     sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
682                     usage()
683                 params = get(xid, dev)
684                 if params is None:
685                     paramslist = []
686                 else:
687                     paramslist = [params]
688             else:
689                 # Show all slices
690                 paramslist = get(None, dev)
691
692             for (xid, share,
693                  minrate, maxrate,
694                  minexemptrate, maxexemptrate,
695                  bytes, exemptbytes) in paramslist:
696                 slice = get_slice(xid)
697                 if slice is None:
698                     # Orphaned (not associated with a slice) class
699                     slice = "%d?" % xid
700                 if numeric:
701                     print "%s %d %d %d %d %d %d %d" % \
702                           (slice, share,
703                            minrate, maxrate,
704                            minexemptrate, maxexemptrate,
705                            bytes, exemptbytes)
706                 else:
707                     print "%s %d %s %s %s %s %s %s" % \
708                           (slice, share,
709                            format_tc_rate(minrate), format_tc_rate(maxrate),
710                            format_tc_rate(minexemptrate), format_tc_rate(maxexemptrate),
711                            format_bytes(bytes), format_bytes(exemptbytes))
712
713         elif len(argv) >= 2:
714             # slice, ...
715             xid = get_xid(argv[1])
716             if xid is None:
717                 sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
718                 usage()
719
720             if argv[0] == "on" or argv[0] == "add" or argv[0] == "replace" or argv[0] == "set":
721                 # Enable cap
722                 args = []
723                 if len(argv) >= 3:
724                     # ... share, minrate, maxrate, minexemptrate, maxexemptrate
725                     casts = [int, get_tc_rate, get_tc_rate, get_tc_rate, get_tc_rate]
726                     for i, arg in enumerate(argv[2:]):
727                         if i >= len(casts):
728                             break
729                         if arg == "-":
730                             args.append(None)
731                         else:
732                             args.append(casts[i](arg))
733                 on(xid, dev, *args)
734
735             elif argv[0] == "off" or argv[0] == "del":
736                 # Disable cap
737                 off(xid, dev)
738
739             else:
740                 usage()
741
742         else:
743             usage()
744
745
746 if __name__ == '__main__':
747     main()