- on: sanity check exempt rates
[util-vserver.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.14 2006/12/13 21:48:14 faiyaza 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     file("/proc/sys/vnet/root_class", "w").write("%d" % ((1 << 16) | default_minor | root_xid))
353
354     # Set up the default class. Packets that fail classification end
355     # up here.
356     on(default_xid, dev, share = default_share)
357
358     # Restore old settings
359     for (xid, share,
360          minrate, maxrate,
361          minexemptrate, maxexemptrate,
362          bytes, exemptbytes) in paramslist:
363         if xid not in (root_xid, default_xid):
364             on(xid, dev, share, minrate, maxrate, minexemptrate, maxexemptrate)
365
366
367 def get(xid = None, dev = dev):
368     """
369     Get the bandwidth limits and current byte totals for a
370     particular slice xid as a tuple (xid, share, minrate, maxrate,
371     minexemptrate, maxexemptrate, bytes, exemptbytes), or all classes
372     as a list of such tuples.
373     """
374
375     if xid is None:
376         ret = []
377     else:
378         ret = None
379
380     rates = {}
381     rate = None
382
383     # ...
384     # class htb 1:1000 parent 1:10 leaf 1000: prio 0 quantum 8000 rate 8bit ceil 10000Kbit ...
385     #  Sent 6851486 bytes 49244 pkt (dropped 0, overlimits 0 requeues 0)
386     # ...
387     # class htb 1:2000 parent 1:20 leaf 2000: prio 0 quantum 8000 rate 8bit ceil 1000Mbit ...
388     #  Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) 
389     # ...
390     for line in tc("-s -d class show dev %s" % dev):
391         # Rate parameter line
392         params = re.match(r"class htb 1:([0-9a-f]+) parent 1:(10|20)", line)
393         # Statistics line
394         stats = re.match(r".* Sent ([0-9]+) bytes", line)
395         # Another class
396         ignore = re.match(r"class htb", line)
397
398         if params is not None:
399             # Which class
400             if params.group(2) == "10":
401                 min = 'min'
402                 max = 'max'
403                 bytes = 'bytes'
404             else:
405                 min = 'minexempt'
406                 max = 'maxexempt'
407                 bytes = 'exemptbytes'
408
409             # Slice ID
410             id = int(params.group(1), 16) & 0x0FFF;
411
412             if rates.has_key(id):
413                 rate = rates[id]
414             else:
415                 rate = {'id': id}
416
417             # Parse share
418             rate['share'] = 1
419             m = re.search(r"quantum (\d+)", line)
420             if m is not None:
421                 rate['share'] = int(m.group(1)) / quantum
422
423             # Parse minrate
424             rate[min] = bwmin
425             m = re.search(r"rate (\w+)", line)
426             if m is not None:
427                 rate[min] = get_tc_rate(m.group(1))
428
429             # Parse maxrate
430             rate[max] = bwmax
431             m = re.search(r"ceil (\w+)", line)
432             if m is not None:
433                 rate[max] = get_tc_rate(m.group(1))
434
435             # Which statistics to parse
436             rate['stats'] = bytes
437
438             rates[id] = rate
439
440         elif stats is not None:
441             if rate is not None:
442                 rate[rate['stats']] = int(stats.group(1))
443
444         elif ignore is not None:
445             rate = None
446
447         # Keep parsing until we get everything
448         if rate is not None and \
449            rate.has_key('min') and rate.has_key('minexempt') and \
450            rate.has_key('max') and rate.has_key('maxexempt') and \
451            rate.has_key('bytes') and rate.has_key('exemptbytes'):
452             params = (rate['id'], rate['share'],
453                       rate['min'], rate['max'],
454                       rate['minexempt'], rate['maxexempt'],
455                       rate['bytes'], rate['exemptbytes'])
456             if xid is None:
457                 # Return a list of parameters
458                 ret.append(params)
459                 rate = None
460             elif xid == rate['id']:
461                 # Return the parameters for this class
462                 ret = params
463                 break
464
465     return ret
466
467
468 def on(xid, dev = dev, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None):
469     """
470     Apply specified bandwidth limit to the specified slice xid
471     """
472
473     # Get defaults from current state if available
474     cap = get(xid, dev)
475     if cap is not None:
476         if share is None:
477             share = cap[1]
478         if minrate is None:
479             minrate = cap[2]
480         if maxrate is None:
481             maxrate = cap[3]
482         if minexemptrate is None:
483             minexemptrate = cap[4]
484         if maxexemptrate is None:
485             maxexemptrate = cap[5]
486
487     # Figure out what the current node bandwidth cap is
488     bwcap = get_bwcap()
489
490     # Set defaults
491     if share is None:
492         share = default_share
493     if minrate is None:
494         minrate = bwmin
495     else:
496         minrate = get_tc_rate(minrate)
497     if maxrate is None:
498         maxrate = bwcap
499     else:
500         maxrate = get_tc_rate(maxrate)
501     if minexemptrate is None:
502         minexemptrate = minrate
503     else:
504         minexemptrate = get_tc_rate(minexemptrate)
505     if maxexemptrate is None:
506         maxexemptrate = bwmax
507     else:
508         maxexemptrate = get_tc_rate(maxexemptrate)
509
510     # Sanity checks
511     if maxrate < bwmin:
512         maxrate = bwmin
513     if maxrate > bwcap:
514         maxrate = bwcap
515     if minrate < bwmin:
516         minrate = bwmin
517     if minrate > maxrate:
518         minrate = maxrate
519     if maxexemptrate < bwmin:
520         maxexemptrate = bwmin
521     if maxexemptrate > bwmax:
522         maxexemptrate = bwmax
523     if minexemptrate < bwmin:
524         minexemptrate = bwmin
525     if minexemptrate > maxexemptrate:
526         minexemptrate = maxexemptrate
527
528     # Set up subclasses for the slice
529     tc("class replace dev %s parent 1:10 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
530        (dev, default_minor | xid, minrate, maxrate, share * quantum))
531
532     tc("class replace dev %s parent 1:20 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
533        (dev, exempt_minor | xid, minexemptrate, maxexemptrate, share * quantum))
534
535     # Attach a FIFO to each subclass, which helps to throttle back
536     # processes that are sending faster than the token buckets can
537     # support.
538     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
539        (dev, default_minor | xid, default_minor | xid))
540
541     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
542        (dev, exempt_minor | xid, exempt_minor | xid))
543
544
545 def set(xid, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None):
546     on(xid = xid, share = share,
547        minrate = minrate, maxrate = maxrate,
548        minexemptrate = minexemptrate, maxexemptrate = maxexemptrate)
549
550
551 # Remove class associated with specified slice xid. If further packets
552 # are seen from this slice, they will be classified into the default
553 # class 1:1FFF.
554 def off(xid, dev = dev):
555     """
556     Remove class associated with specified slice xid. If further
557     packets are seen from this slice, they will be classified into the
558     default class 1:1FFF.
559     """
560
561     cap = get(xid, dev)
562     if cap is not None:
563         tc("class del dev %s classid 1:%x" % (dev, default_minor | xid))
564         tc("class del dev %s classid 1:%x" % (dev, exempt_minor | xid))
565
566
567 def exempt_init(group_name, node_ips):
568     """
569     Initialize the list of destinations exempt from the node bandwidth
570     (burst) cap.
571     """
572
573     # Clean up
574     iptables = "/sbin/iptables -t vnet %s POSTROUTING" 
575     run(iptables % "-F")
576     run("/sbin/ipset -X " + group_name)
577
578     # Create a hashed IP set of all of these destinations
579     lines = ["-N %s iphash" % group_name]
580     add_cmd = "-A %s " % group_name
581     lines += [(add_cmd + ip) for ip in node_ips]
582     lines += ["COMMIT"]
583     restore = "\n".join(lines) + "\n"
584     run("/sbin/ipset -R", restore)
585
586     # Add rule to match on destination IP set
587     run((iptables + " -m set --set %s dst -j CLASSIFY --set-class 1:%x") %
588         ("-A", group_name, exempt_minor))
589
590
591 def usage():
592     bwcap_description = format_tc_rate(get_bwcap())
593         
594     print """
595 Usage:
596
597 %s [OPTION]... [COMMAND] [ARGUMENT]...
598
599 Options:
600         -d device       Network interface (default: %s)
601         -r rate         Node bandwidth cap (default: %s)
602         -q quantum      Share multiplier (default: %d bytes)
603         -n              Print rates in numeric bits per second
604         -v              Enable verbose debug messages
605         -h              This message
606
607 Commands:
608         init
609                 (Re)initialize all bandwidth parameters
610         on slice [share|-] [minrate|-] [maxrate|-] [minexemptrate|-] [maxexemptrate|-]
611                 Set bandwidth parameter(s) for the specified slice
612         off slice
613                 Remove all bandwidth parameters for the specified slice
614         get
615                 Get all bandwidth parameters for all slices
616         get slice
617                 Get bandwidth parameters for the specified slice
618 """ % (sys.argv[0], dev, bwcap_description, quantum)
619     sys.exit(1)
620     
621
622 def main():
623     global dev, quantum, verbose
624
625     # Defaults
626     numeric = False
627     bwcap = get_bwcap()
628
629     (opts, argv) = getopt.getopt(sys.argv[1:], "d:nr:q:vh")
630     for (opt, optval) in opts:
631         if opt == '-d':
632             dev = optval
633         elif opt == '-n':
634             numeric = True
635         elif opt == '-r':
636             bwcap = get_tc_rate(optval)
637         elif opt == '-q':
638             quantum = int(optval)
639         elif opt == '-v':
640             verbose += 1
641         elif opt == '-h':
642             usage()
643
644     if len(argv):
645         if argv[0] == "init" or (argv[0] == "on" and len(argv) == 1):
646             # (Re)initialize
647             init(dev, get_tc_rate(bwcap))
648
649         elif argv[0] == "get" or argv[0] == "show":
650             # Show
651             if len(argv) >= 2:
652                 # Show a particular slice
653                 xid = get_xid(argv[1])
654                 if xid is None:
655                     sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
656                     usage()
657                 params = get(xid, dev)
658                 if params is None:
659                     paramslist = []
660                 else:
661                     paramslist = [params]
662             else:
663                 # Show all slices
664                 paramslist = get(None, dev)
665
666             for (xid, share,
667                  minrate, maxrate,
668                  minexemptrate, maxexemptrate,
669                  bytes, exemptbytes) in paramslist:
670                 slice = get_slice(xid)
671                 if slice is None:
672                     # Orphaned (not associated with a slice) class
673                     slice = "%d?" % xid
674                 if numeric:
675                     print "%s %d %d %d %d %d %d %d" % \
676                           (slice, share,
677                            minrate, maxrate,
678                            minexemptrate, maxexemptrate,
679                            bytes, exemptbytes)
680                 else:
681                     print "%s %d %s %s %s %s %d %d" % \
682                           (slice, share,
683                            format_tc_rate(minrate), format_tc_rate(maxrate),
684                            format_tc_rate(minexemptrate), format_tc_rate(maxexemptrate),
685                            bytes, exemptbytes)
686
687         elif len(argv) >= 2:
688             # slice, ...
689             xid = get_xid(argv[1])
690             if xid is None:
691                 sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
692                 usage()
693
694             if argv[0] == "on" or argv[0] == "add" or argv[0] == "replace" or argv[0] == "set":
695                 # Enable cap
696                 args = []
697                 if len(argv) >= 3:
698                     # ... share, minrate, maxrate, minexemptrate, maxexemptrate
699                     casts = [int, get_tc_rate, get_tc_rate, get_tc_rate, get_tc_rate]
700                     for i, arg in enumerate(argv[2:]):
701                         if i >= len(casts):
702                             break
703                         if arg == "-":
704                             args.append(None)
705                         else:
706                             args.append(casts[i](arg))
707                 on(xid, dev, *args)
708
709             elif argv[0] == "off" or argv[0] == "del":
710                 # Disable cap
711                 off(xid, dev)
712
713             else:
714                 usage()
715
716         else:
717             usage()
718
719
720 if __name__ == '__main__':
721     main()