If it's busy, assume it's already mounted.
[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 = 8
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
195 def format_tc_rate(rate):
196     """
197     Formats a bits/second rate into a tc rate string
198     """
199
200     if rate >= 1000000000 and (rate % 1000000000) == 0:
201         return "%.0fgbit" % (rate / 1000000000.)
202     elif rate >= 1000000 and (rate % 1000000) == 0:
203         return "%.0fmbit" % (rate / 1000000.)
204     elif rate >= 1000:
205         return "%.0fkbit" % (rate / 1000.)
206     else:
207         return "%.0fbit" % rate
208
209
210 # Parse /etc/planetlab/bwcap (or equivalent)
211 def read_bwcap(bwcap_file):
212     bwcap = bwmax
213     try:
214         fp = open(bwcap_file, "r")
215         line = fp.readline().strip()
216         if line:
217             bwcap = get_tc_rate(line)
218     except:
219         pass
220     if bwcap == -1:
221         bwcap = bwmax
222     return bwcap
223
224
225 def get_bwcap(dev = dev):
226     """
227     Get the current (live) value of the node bandwidth cap
228     """
229
230     state = tc("-d class show dev %s" % dev)
231     base_re = re.compile(r"class htb 1:10 parent 1:1 .*ceil ([^ ]+) .*")
232     base_classes = filter(None, map(base_re.match, state))
233     if not base_classes:
234         return -1
235     if len(base_classes) > 1:
236         raise Exception, "unable to get current bwcap"
237     return get_tc_rate(base_classes[0].group(1))
238
239
240 def get_slice(xid):
241     """
242     Get slice name ("princeton_mlh") from slice xid (500)
243     """
244
245     if xid == root_xid:
246         return "root"
247     if xid == default_xid:
248         return "default"
249     try:
250         return pwd.getpwuid(xid).pw_name
251     except KeyError:
252         pass
253
254     return None
255
256 def get_xid(slice):
257     """
258     Get slice xid ("princeton_mlh") from slice name ("500" or "princeton_mlh")
259     """
260
261     if slice == "root":
262         return root_xid
263     if slice == "default":
264         return default_xid
265     try:
266         try:
267             return int(slice)
268         except ValueError:
269             pass
270         return pwd.getpwnam(slice).pw_uid
271     except KeyError:
272         pass
273
274     return None
275
276 def run(cmd, input = None):
277     """
278     Shortcut for running a shell command
279     """
280
281     try:
282         if verbose:
283             sys.stderr.write("Executing: " + cmd + "\n")
284         if input is None:
285             fileobj = os.popen(cmd, "r")
286             output = fileobj.readlines()
287         else:
288             fileobj = os.popen(cmd, "w")
289             fileobj.write(input)
290             output = None
291         if fileobj.close() is None:
292             return output
293     except Exception, e:
294         pass
295     return None
296
297
298 def tc(cmd):
299     """
300     Shortcut for running a tc command
301     """
302
303     return run(TC + " " + cmd)
304
305
306 def init(dev = dev, bwcap = bwmax):
307     """
308     (Re)initialize the bandwidth limits on this node
309     """
310
311     # Load the module used to manage exempt classes
312     run("/sbin/modprobe ip_set_iphash")
313
314     # Save current settings
315     paramslist = get(None, dev)
316
317     # Delete root qdisc 1: if it exists. This will also automatically
318     # delete any child classes.
319     for line in tc("qdisc show dev %s" % dev):
320         # Search for the root qdisc 1:
321         m = re.match(r"qdisc htb 1:", line)
322         if m is not None:
323             tc("qdisc del dev %s root handle 1:" % dev)
324             break
325
326     # Initialize HTB. The "default" clause specifies that if a packet
327     # fails classification, it should go into the class with handle
328     # 1FFF.
329     tc("qdisc add dev %s root handle 1: htb default %x" % \
330        (dev, default_minor | default_xid))
331
332     # Set up a parent class from which all subclasses borrow.
333     tc("class add dev %s parent 1: classid 1:1 htb rate %dbit" % \
334        (dev, bwmax))
335
336     # Set up a subclass that represents the node bandwidth cap. We
337     # allow each slice to borrow up to this rate, so it is also
338     # usually the "ceil" rate for each slice.
339     tc("class add dev %s parent 1:1 classid 1:10 htb rate %dbit ceil %dbit" % \
340        (dev, bwmin, bwcap))
341
342     # Set up a subclass that represents "exemption" from the node
343     # bandwidth cap. Once the node bandwidth cap is reached, bandwidth
344     # to exempt destinations can still be fairly shared up to bwmax.
345     tc("class add dev %s parent 1:1 classid 1:20 htb rate %dbit ceil %dbit" % \
346        (dev, bwmin, bwmax))
347
348     # Set up the root class (and tell VNET what it is). Packets sent
349     # by root end up here and are capped at the node bandwidth
350     # cap.
351     #on(root_xid, dev, share = root_share)
352     #try:
353     #    file("/proc/sys/vnet/root_class", "w").write("%d" % ((1 << 16) | default_minor | root_xid))
354     #except:
355     #    pass
356
357     # Set up the default class. Packets that fail classification end
358     # up here.
359     on(default_xid, dev, share = default_share)
360
361     # Restore old settings
362     for (xid, share,
363          minrate, maxrate,
364          minexemptrate, maxexemptrate,
365          bytes, exemptbytes) in paramslist:
366         if xid not in (root_xid, default_xid):
367             on(xid, dev, share, minrate, maxrate, minexemptrate, maxexemptrate)
368
369
370 def get(xid = None, dev = dev):
371     """
372     Get the bandwidth limits and current byte totals for a
373     particular slice xid as a tuple (xid, share, minrate, maxrate,
374     minexemptrate, maxexemptrate, bytes, exemptbytes), or all classes
375     as a list of such tuples.
376     """
377
378     if xid is None:
379         ret = []
380     else:
381         ret = None
382
383     rates = {}
384     rate = None
385
386     # ...
387     # class htb 1:1000 parent 1:10 leaf 1000: prio 0 quantum 8000 rate 8bit ceil 10000Kbit ...
388     #  Sent 6851486 bytes 49244 pkt (dropped 0, overlimits 0 requeues 0)
389     # ...
390     # class htb 1:2000 parent 1:20 leaf 2000: prio 0 quantum 8000 rate 8bit ceil 1000Mbit ...
391     #  Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) 
392     # ...
393     for line in tc("-s -d class show dev %s" % dev):
394         # Rate parameter line
395         params = re.match(r"class htb 1:([0-9a-f]+) parent 1:(10|20)", line)
396         # Statistics line
397         stats = re.match(r".* Sent ([0-9]+) bytes", line)
398         # Another class
399         ignore = re.match(r"class htb", line)
400
401         if params is not None:
402             # Which class
403             if params.group(2) == "10":
404                 min = 'min'
405                 max = 'max'
406                 bytes = 'bytes'
407             else:
408                 min = 'minexempt'
409                 max = 'maxexempt'
410                 bytes = 'exemptbytes'
411
412             # Slice ID
413             id = int(params.group(1), 16) & 0x0FFF;
414
415             if rates.has_key(id):
416                 rate = rates[id]
417             else:
418                 rate = {'id': id}
419
420             # Parse share
421             rate['share'] = 1
422             m = re.search(r"quantum (\d+)", line)
423             if m is not None:
424                 rate['share'] = int(m.group(1)) / quantum
425
426             # Parse minrate
427             rate[min] = bwmin
428             m = re.search(r"rate (\w+)", line)
429             if m is not None:
430                 rate[min] = get_tc_rate(m.group(1))
431
432             # Parse maxrate
433             rate[max] = bwmax
434             m = re.search(r"ceil (\w+)", line)
435             if m is not None:
436                 rate[max] = get_tc_rate(m.group(1))
437
438             # Which statistics to parse
439             rate['stats'] = bytes
440
441             rates[id] = rate
442
443         elif stats is not None:
444             if rate is not None:
445                 rate[rate['stats']] = int(stats.group(1))
446
447         elif ignore is not None:
448             rate = None
449
450         # Keep parsing until we get everything
451         if rate is not None and \
452            rate.has_key('min') and rate.has_key('minexempt') and \
453            rate.has_key('max') and rate.has_key('maxexempt') and \
454            rate.has_key('bytes') and rate.has_key('exemptbytes'):
455             params = (rate['id'], rate['share'],
456                       rate['min'], rate['max'],
457                       rate['minexempt'], rate['maxexempt'],
458                       rate['bytes'], rate['exemptbytes'])
459             if xid is None:
460                 # Return a list of parameters
461                 ret.append(params)
462                 rate = None
463             elif xid == rate['id']:
464                 # Return the parameters for this class
465                 ret = params
466                 break
467
468     return ret
469
470
471 def on(xid, dev = dev, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None):
472     """
473     Apply specified bandwidth limit to the specified slice xid
474     """
475
476     # Get defaults from current state if available
477     cap = get(xid, dev)
478     if cap is not None:
479         if share is None:
480             share = cap[1]
481         if minrate is None:
482             minrate = cap[2]
483         if maxrate is None:
484             maxrate = cap[3]
485         if minexemptrate is None:
486             minexemptrate = cap[4]
487         if maxexemptrate is None:
488             maxexemptrate = cap[5]
489
490     # Figure out what the current node bandwidth cap is
491     bwcap = get_bwcap()
492
493     # Set defaults
494     if share is None:
495         share = default_share
496     if minrate is None:
497         minrate = bwmin
498     else:
499         minrate = get_tc_rate(minrate)
500     if maxrate is None:
501         maxrate = bwcap
502     else:
503         maxrate = get_tc_rate(maxrate)
504     if minexemptrate is None:
505         minexemptrate = minrate
506     else:
507         minexemptrate = get_tc_rate(minexemptrate)
508     if maxexemptrate is None:
509         maxexemptrate = bwmax
510     else:
511         maxexemptrate = get_tc_rate(maxexemptrate)
512
513     # Sanity checks
514     if maxrate < bwmin:
515         maxrate = bwmin
516     if maxrate > bwcap:
517         maxrate = bwcap
518     if minrate < bwmin:
519         minrate = bwmin
520     if minrate > maxrate:
521         minrate = maxrate
522     if maxexemptrate < bwmin:
523         maxexemptrate = bwmin
524     if maxexemptrate > bwmax:
525         maxexemptrate = bwmax
526     if minexemptrate < bwmin:
527         minexemptrate = bwmin
528     if minexemptrate > maxexemptrate:
529         minexemptrate = maxexemptrate
530
531     # Set up subclasses for the slice
532     tc("class replace dev %s parent 1:10 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
533        (dev, default_minor | xid, minrate, maxrate, share * quantum))
534
535     tc("class replace dev %s parent 1:20 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
536        (dev, exempt_minor | xid, minexemptrate, maxexemptrate, share * quantum))
537
538     # Attach a FIFO to each subclass, which helps to throttle back
539     # processes that are sending faster than the token buckets can
540     # support.
541     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
542        (dev, default_minor | xid, default_minor | xid))
543
544     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
545        (dev, exempt_minor | xid, exempt_minor | xid))
546
547
548 def set(xid, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None):
549     on(xid = xid, share = share,
550        minrate = minrate, maxrate = maxrate,
551        minexemptrate = minexemptrate, maxexemptrate = maxexemptrate)
552
553
554 # Remove class associated with specified slice xid. If further packets
555 # are seen from this slice, they will be classified into the default
556 # class 1:1FFF.
557 def off(xid, dev = dev):
558     """
559     Remove class associated with specified slice xid. If further
560     packets are seen from this slice, they will be classified into the
561     default class 1:1FFF.
562     """
563
564     cap = get(xid, dev)
565     if cap is not None:
566         tc("class del dev %s classid 1:%x" % (dev, default_minor | xid))
567         tc("class del dev %s classid 1:%x" % (dev, exempt_minor | xid))
568
569
570 def exempt_init(group_name, node_ips):
571     """
572     Initialize the list of destinations exempt from the node bandwidth
573     (burst) cap.
574     """
575
576     # Clean up
577     iptables = "/sbin/iptables -t MANGLE %s POSTROUTING" 
578     run(iptables % "-F")
579     run("/sbin/ipset -X " + group_name)
580
581     # Create a hashed IP set of all of these destinations
582     lines = ["-N %s iphash" % group_name]
583     add_cmd = "-A %s " % group_name
584     lines += [(add_cmd + ip) for ip in node_ips]
585     lines += ["COMMIT"]
586     restore = "\n".join(lines) + "\n"
587     run("/sbin/ipset -R", restore)
588
589     # Add rule to match on destination IP set
590     run((iptables + " -m set --set %s dst -j CLASSIFY --set-class 1:%x") %
591         ("-A", group_name, exempt_minor))
592
593
594 def usage():
595     bwcap_description = format_tc_rate(get_bwcap())
596         
597     print """
598 Usage:
599
600 %s [OPTION]... [COMMAND] [ARGUMENT]...
601
602 Options:
603         -d device       Network interface (default: %s)
604         -r rate         Node bandwidth cap (default: %s)
605         -q quantum      Share multiplier (default: %d bytes)
606         -n              Print rates in numeric bits per second
607         -v              Enable verbose debug messages
608         -h              This message
609
610 Commands:
611         init
612                 (Re)initialize all bandwidth parameters
613         on slice [share|-] [minrate|-] [maxrate|-] [minexemptrate|-] [maxexemptrate|-]
614                 Set bandwidth parameter(s) for the specified slice
615         off slice
616                 Remove all bandwidth parameters for the specified slice
617         get
618                 Get all bandwidth parameters for all slices
619         get slice
620                 Get bandwidth parameters for the specified slice
621 """ % (sys.argv[0], dev, bwcap_description, quantum)
622     sys.exit(1)
623     
624
625 def main():
626     global dev, quantum, verbose
627
628     # Defaults
629     numeric = False
630     bwcap = get_bwcap()
631
632     (opts, argv) = getopt.getopt(sys.argv[1:], "d:nr:q:vh")
633     for (opt, optval) in opts:
634         if opt == '-d':
635             dev = optval
636         elif opt == '-n':
637             numeric = True
638         elif opt == '-r':
639             bwcap = get_tc_rate(optval)
640         elif opt == '-q':
641             quantum = int(optval)
642         elif opt == '-v':
643             verbose += 1
644         elif opt == '-h':
645             usage()
646
647     if len(argv):
648         if argv[0] == "init" or (argv[0] == "on" and len(argv) == 1):
649             # (Re)initialize
650             init(dev, get_tc_rate(bwcap))
651
652         elif argv[0] == "get" or argv[0] == "show":
653             # Show
654             if len(argv) >= 2:
655                 # Show a particular slice
656                 xid = get_xid(argv[1])
657                 if xid is None:
658                     sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
659                     usage()
660                 params = get(xid, dev)
661                 if params is None:
662                     paramslist = []
663                 else:
664                     paramslist = [params]
665             else:
666                 # Show all slices
667                 paramslist = get(None, dev)
668
669             for (xid, share,
670                  minrate, maxrate,
671                  minexemptrate, maxexemptrate,
672                  bytes, exemptbytes) in paramslist:
673                 slice = get_slice(xid)
674                 if slice is None:
675                     # Orphaned (not associated with a slice) class
676                     slice = "%d?" % xid
677                 if numeric:
678                     print "%s %d %d %d %d %d %d %d" % \
679                           (slice, share,
680                            minrate, maxrate,
681                            minexemptrate, maxexemptrate,
682                            bytes, exemptbytes)
683                 else:
684                     print "%s %d %s %s %s %s %d %d" % \
685                           (slice, share,
686                            format_tc_rate(minrate), format_tc_rate(maxrate),
687                            format_tc_rate(minexemptrate), format_tc_rate(maxexemptrate),
688                            bytes, exemptbytes)
689
690         elif len(argv) >= 2:
691             # slice, ...
692             xid = get_xid(argv[1])
693             if xid is None:
694                 sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
695                 usage()
696
697             if argv[0] == "on" or argv[0] == "add" or argv[0] == "replace" or argv[0] == "set":
698                 # Enable cap
699                 args = []
700                 if len(argv) >= 3:
701                     # ... share, minrate, maxrate, minexemptrate, maxexemptrate
702                     casts = [int, get_tc_rate, get_tc_rate, get_tc_rate, get_tc_rate]
703                     for i, arg in enumerate(argv[2:]):
704                         if i >= len(casts):
705                             break
706                         if arg == "-":
707                             args.append(None)
708                         else:
709                             args.append(casts[i](arg))
710                 on(xid, dev, *args)
711
712             elif argv[0] == "off" or argv[0] == "del":
713                 # Disable cap
714                 off(xid, dev)
715
716             else:
717                 usage()
718
719         else:
720             usage()
721
722
723 if __name__ == '__main__':
724     main()