| 
596
 | 
     1 #!/usr/bin/env python
 | 
| 
 | 
     2 
 | 
| 
 | 
     3 """
 | 
| 
 | 
     4 multiprocessing/subprocess experiments
 | 
| 
 | 
     5 """
 | 
| 
 | 
     6 
 | 
| 
 | 
     7 import argparse
 | 
| 
 | 
     8 import os
 | 
| 
617
 | 
     9 import subprocess # http://bugs.python.org/issue1731717
 | 
| 
596
 | 
    10 import sys
 | 
| 
 | 
    11 import time
 | 
| 
 | 
    12 import tempfile
 | 
| 
 | 
    13 
 | 
| 
606
 | 
    14 string = (str, unicode)
 | 
| 
 | 
    15 
 | 
| 
601
 | 
    16 class Process(subprocess.Popen):
 | 
| 
 | 
    17     """why would you name a subprocess object Popen?"""
 | 
| 
 | 
    18 
 | 
| 
606
 | 
    19     # http://docs.python.org/2/library/subprocess.html#popen-constructor
 | 
| 
610
 | 
    20     defaults = {'bufsize': 1, # line buffered
 | 
| 
611
 | 
    21                 'store_output': True, # store stdout
 | 
| 
603
 | 
    22                 }
 | 
| 
 | 
    23 
 | 
| 
606
 | 
    24     def __init__(self, command, **kwargs):
 | 
| 
 | 
    25 
 | 
| 
 | 
    26         # setup arguments
 | 
| 
 | 
    27         self.command = command
 | 
| 
 | 
    28         _kwargs = self.defaults.copy()
 | 
| 
 | 
    29         _kwargs.update(kwargs)
 | 
| 
 | 
    30 
 | 
| 
607
 | 
    31         # on unix, ``shell={True|False}`` should always come from the
 | 
| 
 | 
    32         # type of command (string or list)
 | 
| 
609
 | 
    33         if not subprocess.mswindows:
 | 
| 
607
 | 
    34             _kwargs['shell'] = isinstance(command, string)
 | 
| 
 | 
    35 
 | 
| 
606
 | 
    36         # output buffer
 | 
| 
611
 | 
    37         self.location = 0
 | 
| 
606
 | 
    38         self.output_buffer = tempfile.SpooledTemporaryFile()
 | 
| 
611
 | 
    39         self.output = '' if _kwargs.pop('store_output') else None
 | 
| 
606
 | 
    40         _kwargs['stdout'] = self.output_buffer
 | 
| 
 | 
    41 
 | 
| 
611
 | 
    42         # runtime
 | 
| 
 | 
    43         self.start = time.time()
 | 
| 
 | 
    44         self.end = None
 | 
| 
 | 
    45 
 | 
| 
606
 | 
    46         # launch subprocess
 | 
| 
607
 | 
    47         subprocess.Popen.__init__(self, command, **_kwargs)
 | 
| 
606
 | 
    48 
 | 
| 
611
 | 
    49     def _finalize(self, process_output):
 | 
| 
 | 
    50         """internal function to finalize"""
 | 
| 
 | 
    51 
 | 
| 
 | 
    52         # read final output
 | 
| 
 | 
    53         self.read(process_output)
 | 
| 
 | 
    54 
 | 
| 
614
 | 
    55         # reset output buffer location
 | 
| 
611
 | 
    56         self.output_buffer.seek(0)
 | 
| 
 | 
    57 
 | 
| 
 | 
    58         # set end time
 | 
| 
 | 
    59         self.end = time.time()
 | 
| 
 | 
    60 
 | 
| 
614
 | 
    61     def poll(self, process_output=None):
 | 
| 
 | 
    62 
 | 
| 
 | 
    63         if process_output is not None:
 | 
| 
 | 
    64             self.read(process_output) # read from output buffer
 | 
| 
 | 
    65         poll = subprocess.Popen.poll(self)
 | 
| 
 | 
    66         if poll is not None:
 | 
| 
 | 
    67             self._finalize(process_output)
 | 
| 
 | 
    68         return poll
 | 
| 
613
 | 
    69 
 | 
| 
611
 | 
    70     def wait(self, maxtime=None, sleep=1., process_output=None):
 | 
| 
606
 | 
    71         """
 | 
| 
 | 
    72         maxtime -- timeout in seconds
 | 
| 
 | 
    73         sleep -- number of seconds to sleep between polling
 | 
| 
 | 
    74         """
 | 
| 
614
 | 
    75         while self.poll(process_output) is None:
 | 
| 
606
 | 
    76 
 | 
| 
 | 
    77             # check for timeout
 | 
| 
 | 
    78             curr_time = time.time()
 | 
| 
613
 | 
    79             run_time = self.runtime()
 | 
| 
612
 | 
    80             if maxtime is not None and run_time > maxtime:
 | 
| 
611
 | 
    81                 self.kill()
 | 
| 
 | 
    82                 self._finalize(process_output)
 | 
| 
 | 
    83                 return
 | 
| 
606
 | 
    84 
 | 
| 
 | 
    85             # naptime
 | 
| 
 | 
    86             if sleep:
 | 
| 
 | 
    87                 time.sleep(sleep)
 | 
| 
 | 
    88 
 | 
| 
611
 | 
    89         # finalize
 | 
| 
613
 | 
    90         self._finalize(process_output)
 | 
| 
608
 | 
    91 
 | 
| 
 | 
    92         return self.returncode # set by ``.poll()``
 | 
| 
606
 | 
    93 
 | 
| 
611
 | 
    94     def read(self, process_output=None):
 | 
| 
610
 | 
    95         """read from the output buffer"""
 | 
| 
611
 | 
    96 
 | 
| 
610
 | 
    97         self.output_buffer.seek(self.location)
 | 
| 
 | 
    98         read = self.output_buffer.read()
 | 
| 
611
 | 
    99         if self.output is not None:
 | 
| 
 | 
   100             self.output += read
 | 
| 
 | 
   101         if process_output:
 | 
| 
 | 
   102             process_output(read)
 | 
| 
610
 | 
   103         self.location += len(read)
 | 
| 
 | 
   104         return read
 | 
| 
 | 
   105 
 | 
| 
606
 | 
   106     def commandline(self):
 | 
| 
 | 
   107         """returns string of command line"""
 | 
| 
 | 
   108 
 | 
| 
 | 
   109         if isinstance(self.command, string):
 | 
| 
 | 
   110             return self.command
 | 
| 
 | 
   111         return subprocess.list2cmdline(self.command)
 | 
| 
 | 
   112 
 | 
| 
 | 
   113     __str__ = commandline
 | 
| 
 | 
   114 
 | 
| 
611
 | 
   115     def runtime(self):
 | 
| 
 | 
   116         """returns time spent running or total runtime if completed"""
 | 
| 
 | 
   117 
 | 
| 
 | 
   118         if self.end is None:
 | 
| 
613
 | 
   119             return time.time() - self.start
 | 
| 
 | 
   120         return self.end - self.start
 | 
| 
611
 | 
   121 
 | 
| 
596
 | 
   122 
 | 
| 
 | 
   123 def main(args=sys.argv[1:]):
 | 
| 
599
 | 
   124     """CLI"""
 | 
| 
596
 | 
   125 
 | 
| 
615
 | 
   126     description = """demonstration of how to do things with subprocess"""
 | 
| 
 | 
   127 
 | 
| 
600
 | 
   128     # available programs
 | 
| 
 | 
   129     progs = {'yes': ["yes"],
 | 
| 
 | 
   130              'ping': ['ping', 'google.com']}
 | 
| 
 | 
   131 
 | 
| 
599
 | 
   132     # parse command line
 | 
| 
615
 | 
   133     parser = argparse.ArgumentParser(description=description)
 | 
| 
596
 | 
   134     parser.add_argument("-t", "--time", dest="time",
 | 
| 
599
 | 
   135                         type=float, default=4.,
 | 
| 
614
 | 
   136                         help="seconds to run for (or <= 0 for forever)")
 | 
| 
599
 | 
   137     parser.add_argument("-s", "--sleep", dest="sleep",
 | 
| 
596
 | 
   138                         type=float, default=1.,
 | 
| 
600
 | 
   139                         help="sleep this number of seconds between polling")
 | 
| 
 | 
   140     parser.add_argument("-p", "--prog", dest='program',
 | 
| 
606
 | 
   141                         choices=progs.keys(), default='ping',
 | 
| 
600
 | 
   142                         help="subprocess to run")
 | 
| 
606
 | 
   143     parser.add_argument("--list-programs", dest='list_programs',
 | 
| 
 | 
   144                         action='store_true', default=False,
 | 
| 
 | 
   145                         help="list available programs")
 | 
| 
615
 | 
   146     parser.add_argument("--wait", dest='wait',
 | 
| 
 | 
   147                         action='store_true', default=False,
 | 
| 
 | 
   148                         help="run with ``.wait()`` and a callback")
 | 
| 
 | 
   149     parser.add_argument("--callback", dest='callback',
 | 
| 
 | 
   150                         action='store_true', default=False,
 | 
| 
 | 
   151                         help="run with polling and a callback")
 | 
| 
596
 | 
   152     options = parser.parse_args(args)
 | 
| 
 | 
   153 
 | 
| 
608
 | 
   154     # list programs
 | 
| 
 | 
   155     if options.list_programs:
 | 
| 
 | 
   156         for key in sorted(progs.keys()):
 | 
| 
 | 
   157             print ('{}: {}'.format(key, subprocess.list2cmdline(progs[key])))
 | 
| 
610
 | 
   158         sys.exit(0)
 | 
| 
599
 | 
   159 
 | 
| 
 | 
   160     # select program
 | 
| 
600
 | 
   161     prog = progs[options.program]
 | 
| 
599
 | 
   162 
 | 
| 
611
 | 
   163     # start process
 | 
| 
608
 | 
   164     proc = Process(prog)
 | 
| 
596
 | 
   165 
 | 
| 
615
 | 
   166     # demo function for processing output
 | 
| 
 | 
   167     def output_processor(output):
 | 
| 
 | 
   168         print ('[{}]:\n{}\n{}'.format(proc.runtime(),
 | 
| 
 | 
   169                                       output.upper(),
 | 
| 
 | 
   170                                       '-==-'*10))
 | 
| 
 | 
   171     if options.callback:
 | 
| 
 | 
   172         process_output = output_processor
 | 
| 
 | 
   173     else:
 | 
| 
 | 
   174         process_output = None
 | 
| 
613
 | 
   175 
 | 
| 
615
 | 
   176     if options.wait:
 | 
| 
 | 
   177         # wait for being done
 | 
| 
 | 
   178         proc.wait(maxtime=options.time, sleep=options.sleep, process_output=output_processor)
 | 
| 
 | 
   179     else:
 | 
| 
 | 
   180         # start the main subprocess loop
 | 
| 
 | 
   181         while proc.poll(process_output) is None:
 | 
| 
608
 | 
   182 
 | 
| 
615
 | 
   183             if options.time > 0 and proc.runtime() > options.time:
 | 
| 
 | 
   184                 proc.kill()
 | 
| 
596
 | 
   185 
 | 
| 
615
 | 
   186             if options.sleep:
 | 
| 
 | 
   187                 time.sleep(options.sleep)
 | 
| 
 | 
   188 
 | 
| 
616
 | 
   189             if process_output is None:
 | 
| 
615
 | 
   190                 # process the output with ``.read()`` call
 | 
| 
616
 | 
   191                 read = proc.read()
 | 
| 
 | 
   192                 output_processor(read)
 | 
| 
611
 | 
   193 
 | 
| 
614
 | 
   194     # correctness tests
 | 
| 
 | 
   195     assert proc.end is not None
 | 
| 
 | 
   196 
 | 
| 
 | 
   197     # print summary
 | 
| 
611
 | 
   198     output = proc.output
 | 
| 
 | 
   199     n_lines = len(output.splitlines())
 | 
| 
615
 | 
   200     print ("{}: {} lines, ran for {} seconds".format(subprocess.list2cmdline(prog), n_lines, proc.runtime()))
 | 
| 
596
 | 
   201 
 | 
| 
 | 
   202 if __name__ == '__main__':
 | 
| 
 | 
   203     main()
 |