ovs-bugtool: Remove calls of ovs-ofctl on ovs-system.
[sliver-openvswitch.git] / utilities / bugtool / ovs-bugtool.in
index 2074e23..19b3378 100755 (executable)
@@ -76,8 +76,6 @@ FSTAB = '/etc/fstab'
 PROC_MOUNTS = '/proc/mounts'
 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
-LVM_CACHE = '/etc/lvm/cache/.cache'
-LVM_CONFIG = '/etc/lvm/lvm.conf'
 PROC_CPUINFO = '/proc/cpuinfo'
 PROC_MEMINFO = '/proc/meminfo'
 PROC_IOPORTS = '/proc/ioports'
@@ -90,7 +88,6 @@ PROC_FILESYSTEMS = '/proc/filesystems'
 PROC_CMDLINE = '/proc/cmdline'
 PROC_CONFIG = '/proc/config.gz'
 PROC_USB_DEV = '/proc/bus/usb/devices'
-PROC_XEN_BALLOON = '/proc/xen/balloon'
 PROC_NET_BONDING_DIR = '/proc/net/bonding'
 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
 ROUTE_RE = re.compile(r'^.*/route-.*')
@@ -117,17 +114,10 @@ OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch'    # RHEL
 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
 OPENVSWITCH_CONF_DB = '@DBDIR@/conf.db'
 OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
-COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
 VAR_LOG_DIR = '/var/log/'
 VAR_LOG_CORE_DIR = '/var/log/core'
-X11_LOGS_DIR = VAR_LOG_DIR
-X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
-X11_AUTH_DIR = '/root/'
-X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
 YUM_LOG = '/var/log/yum.log'
 YUM_REPOS_DIR = '/etc/yum.repos.d'
-PAM_DIR = '/etc/pam.d'
-KRB5_CONF = '/etc/krb5.conf'
 
 #
 # External programs
@@ -135,7 +125,6 @@ KRB5_CONF = '/etc/krb5.conf'
 
 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
 ARP = 'arp'
-BRCTL = 'brctl'
 CAT = 'cat'
 CHKCONFIG = 'chkconfig'
 DF = 'df'
@@ -146,15 +135,12 @@ DPKG_QUERY = 'dpkg-query'
 ETHTOOL = 'ethtool'
 FDISK = 'fdisk'
 FIND = 'find'
-HDPARM = 'hdparm'
 IFCONFIG = 'ifconfig'
 IPTABLES = 'iptables'
 ISCSIADM = 'iscsiadm'
 LOSETUP = 'losetup'
 LS = 'ls'
 LSPCI = 'lspci'
-LVDISPLAY = 'lvdisplay'
-LVS = 'lvs'
 MD5SUM = 'md5sum'
 MODINFO = 'modinfo'
 MPPUTIL = 'mppUtil'
@@ -165,15 +151,12 @@ OVS_OFCTL = 'ovs-ofctl'
 OVS_VSCTL = 'ovs-vsctl'
 OVS_APPCTL = 'ovs-appctl'
 PS = 'ps'
-PVS = 'pvs'
 ROUTE = 'route'
 RPM = 'rpm'
 SG_MAP = 'sg_map'
 SYSCTL = 'sysctl'
 TC = 'tc'
 UPTIME = 'uptime'
-VGS = 'vgs'
-VGSCAN = 'vgscan'
 ZCAT = 'zcat'
 
 #
@@ -215,29 +198,18 @@ CAP_XML_ROOT = "system-status-capabilities"
 CAP_XML_ELEMENT = 'capability'
 
 
-CAP_BLOBS                = 'blobs'
 CAP_BOOT_LOADER          = 'boot-loader'
-CAP_COLLECTD_LOGS        = 'collectd-logs'
 CAP_DISK_INFO            = 'disk-info'
-CAP_FIRSTBOOT            = 'firstboot'
 CAP_HARDWARE_INFO        = 'hardware-info'
-CAP_HDPARM_T             = 'hdparm-t'
-CAP_HIGH_AVAILABILITY    = 'high-availability'
 CAP_KERNEL_INFO          = 'kernel-info'
 CAP_LOSETUP_A            = 'loopback-devices'
 CAP_MULTIPATH            = 'multipath'
 CAP_NETWORK_CONFIG       = 'network-config'
 CAP_NETWORK_STATUS       = 'network-status'
-CAP_OEM                  = 'oem'
-CAP_PAM                  = 'pam'
+CAP_OPENVSWITCH_LOGS    = 'ovs-system-logs'
 CAP_PROCESS_LIST         = 'process-list'
-CAP_PERSISTENT_STATS     = 'persistent-stats'
 CAP_SYSTEM_LOGS          = 'system-logs'
 CAP_SYSTEM_SERVICES      = 'system-services'
-CAP_VNCTERM              = 'vncterm'
-CAP_WLB                  = 'wlb'
-CAP_X11_LOGS             = 'X11'
-CAP_X11_AUTH             = 'X11-auth'
 CAP_YUM                  = 'yum'
 
 KB = 1024
@@ -247,6 +219,8 @@ caps = {}
 cap_sizes = {}
 unlimited_data = False
 dbg = False
+# Default value for the number of rotated logs.
+log_days = 20
 
 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
         max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
@@ -255,19 +229,12 @@ def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
     cap_sizes[key] = 0
 
 
-cap(CAP_BLOBS,               PII_NO,                    max_size=5*MB)
 cap(CAP_BOOT_LOADER,         PII_NO,                    max_size=3*KB,
     max_time=5)
-cap(CAP_COLLECTD_LOGS,       PII_MAYBE,                 max_size=50*MB,
-    max_time=5)
 cap(CAP_DISK_INFO,           PII_MAYBE,                 max_size=50*KB,
     max_time=20)
-cap(CAP_FIRSTBOOT,           PII_YES,   min_size=60*KB, max_size=80*KB)
 cap(CAP_HARDWARE_INFO,       PII_MAYBE,                 max_size=30*KB,
     max_time=20)
-cap(CAP_HDPARM_T,            PII_NO,    min_size=0,     max_size=5*KB,
-    min_time=20, max_time=90, checked=False, hidden=True)
-cap(CAP_HIGH_AVAILABILITY,   PII_MAYBE,                 max_size=5*MB)
 cap(CAP_KERNEL_INFO,         PII_MAYBE,                 max_size=120*KB,
     max_time=5)
 cap(CAP_LOSETUP_A,           PII_MAYBE,                 max_size=KB, max_time=5)
@@ -275,22 +242,16 @@ cap(CAP_MULTIPATH,           PII_MAYBE,                 max_size=20*KB,
     max_time=10)
 cap(CAP_NETWORK_CONFIG,      PII_IF_CUSTOMIZED,
                                         min_size=0,     max_size=40*KB)
-cap(CAP_NETWORK_STATUS,      PII_YES,                   max_size=50*KB,
+cap(CAP_NETWORK_STATUS,      PII_YES,                   max_size=50*MB,
     max_time=30)
-cap(CAP_PAM,                 PII_NO,                    max_size=50*KB)
-cap(CAP_PERSISTENT_STATS,    PII_MAYBE,                 max_size=50*MB,
-    max_time=60)
+cap(CAP_OPENVSWITCH_LOGS,    PII_MAYBE,                 max_size=-1,
+    max_time=5)
 cap(CAP_PROCESS_LIST,        PII_YES,                   max_size=30*KB,
     max_time=20)
-cap(CAP_SYSTEM_LOGS,         PII_MAYBE,                 max_size=50*MB,
+cap(CAP_SYSTEM_LOGS,         PII_MAYBE,                 max_size=200*MB,
     max_time=5)
 cap(CAP_SYSTEM_SERVICES,     PII_NO,                    max_size=5*KB,
     max_time=20)
-cap(CAP_VNCTERM,             PII_MAYBE, checked = False)
-cap(CAP_WLB,                 PII_NO,                    max_size=3*MB,
-    max_time=20)
-cap(CAP_X11_LOGS,            PII_NO,                    max_size=100*KB)
-cap(CAP_X11_AUTH,            PII_NO,                    max_size=100*KB)
 cap(CAP_YUM,                 PII_IF_CUSTOMIZED,         max_size=10*KB,
     max_time=30)
 
@@ -308,7 +269,7 @@ def output(x):
 def output_ts(x):
     output("[%s]  %s" % (time.strftime("%x %X %Z"), x))
 
-def cmd_output(cap, args, label=None, filter=None):
+def cmd_output(cap, args, label=None, filter=None, binary=False):
     if cap in entries:
         if not label:
             if isinstance(args, list):
@@ -317,7 +278,8 @@ def cmd_output(cap, args, label=None, filter=None):
                 label = ' '.join(a)
             else:
                 label = args
-        data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
+        data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
+                       'binary': binary}
 
 def file_output(cap, path_list, newest_first=False):
     """
@@ -361,6 +323,16 @@ def func_output(cap, label, func):
         t = str(func).split()
         data[label] = {'cap': cap, 'func': func}
 
+def log_output(cap, logs, newest_first=False):
+    global log_days
+    file_output(cap, logs)
+    file_output(cap,
+        ['%s.%d' % (f, n) for n in range(1, log_days+1) for f in logs], \
+        newest_first=newest_first)
+    file_output(cap,
+        ['%s.%d.gz' % (f, n) for n in range(1, log_days+1) for f in logs], \
+        newest_first=newest_first)
+
 def collect_data():
     process_lists = {}
 
@@ -370,7 +342,9 @@ def collect_data():
             v['output'] = StringIOmtime()
             if not process_lists.has_key(cap):
                 process_lists[cap] = []
-            process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
+            process_lists[cap].append(
+                ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
+                           v['filter'], v['binary']))
         elif v.has_key('filename') and v['filename'].startswith('/proc/'):
             # proc files must be read into memory
             try:
@@ -402,7 +376,7 @@ def collect_data():
 
 def main(argv=None):
     global ANSWER_YES_TO_ALL, SILENT_MODE
-    global entries, data, dbg, unlimited_data
+    global entries, data, dbg, unlimited_data, log_days
 
     # Filter flags
     only_ovs_info = False
@@ -414,7 +388,7 @@ def main(argv=None):
         return 1
 
     output_file = None
-    output_type = 'tar.bz2'
+    output_type = 'tar.gz'
     output_fd = -1
 
     if argv is None:
@@ -424,7 +398,7 @@ def main(argv=None):
         (options, params) = getopt.gnu_getopt(
             argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
                          'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
-                         'debug', 'ovs'])
+                         'debug', 'ovs', 'log-days='])
     except getopt.GetoptError, opterr:
         print >>sys.stderr, opterr
         return 2
@@ -486,6 +460,9 @@ def main(argv=None):
             only_ovs_info = True
             collect_all_info = False
 
+        if k == '--log-days':
+            log_days = int(v)
+
     if len(params) != 1:
         print >>sys.stderr, "Invalid additional arguments", str(params)
         return 2
@@ -521,27 +498,18 @@ exclude those logs from the archive.
     cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
     cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
 
-    tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
     cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
     file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
     file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
     cmd_output(CAP_DISK_INFO, [DF, '-alT'])
     cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
-    for d in disk_list():
-        cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
     if len(pidof('iscsid')) != 0:
         cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
-    cmd_output(CAP_DISK_INFO, [VGSCAN])
-    cmd_output(CAP_DISK_INFO, [PVS])
-    cmd_output(CAP_DISK_INFO, [VGS])
-    cmd_output(CAP_DISK_INFO, [LVS])
-    file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
     cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
     func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
-    cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
 
     file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
     cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
@@ -550,10 +518,7 @@ exclude those logs from the archive.
     file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
     file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
     cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
-    # FIXME IDE?
 
-    for d in disk_list():
-        cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
 
     file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
                                   PROC_FILESYSTEMS, PROC_CMDLINE])
@@ -584,7 +549,6 @@ exclude those logs from the archive.
     cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
     for dir in DHCP_LEASE_DIR:
         tree_output(CAP_NETWORK_STATUS, dir)
-    cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'show'])
     cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
     for p in os.listdir('/sys/class/net/'):
         try:
@@ -593,11 +557,12 @@ exclude those logs from the archive.
             f.close()
             if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
                 # ARPHRD_ETHER
-                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
-                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
-                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
-                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
+                if not p.startswith('vif') and not p.startswith('tap'):
+                    cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
+                    cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
+                    cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
+                    cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
             if int(t) == 1:
                 cmd_output(CAP_NETWORK_STATUS,
                            [TC, '-s', '-d', 'class', 'show', 'dev', p])
@@ -610,8 +575,6 @@ exclude those logs from the archive.
     if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
         cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
         for d in dp_list():
-            cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
-            cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
         try:
             vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
@@ -624,31 +587,23 @@ exclude those logs from the archive.
         except e:
             pass
 
-    tree_output(CAP_PAM, PAM_DIR)
-    file_output(CAP_PAM, [KRB5_CONF])
-
     cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
     func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
 
-    logs = ([ VAR_LOG_DIR + x for x in
-             [ 'crit.log', 'kern.log', 'daemon.log', 'user.log',
-             'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot' ]]
-            + [ OPENVSWITCH_LOG_DIR + x for x in
-                [ 'ovs-vswitchd.log', 'ovs-brcompatd.log', 'ovsdb-server.log',
-                  'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log' ]])
-    file_output(CAP_SYSTEM_LOGS, logs)
-    file_output(CAP_SYSTEM_LOGS,
-                [ '%s.%d' % (f, n) for n in range(20) for f in logs ])
-    file_output(CAP_SYSTEM_LOGS,
-                [ '%s.%d.gz' % (f, n) for n in range(20) for f in logs ])
+    system_logs = ([ VAR_LOG_DIR + x for x in
+        ['crit.log', 'kern.log', 'daemon.log', 'user.log',
+        'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
+    ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
+        ['ovs-vswitchd.log', 'ovsdb-server.log',
+        'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
+    log_output(CAP_SYSTEM_LOGS, system_logs)
+    log_output(CAP_OPENVSWITCH_LOGS, ovs_logs)
 
     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
 
     cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
 
-    tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
-    tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
     tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
 
     file_output(CAP_YUM, [YUM_LOG])
@@ -726,8 +681,6 @@ exclude those logs from the archive.
     else:
         make_zip(subdir, output_file)
 
-    clean_tapdisk_logs()
-
     if dbg:
         print >>sys.stderr, "Category sizes (max, actual):\n"
         for c in caps.keys():
@@ -735,39 +688,6 @@ exclude those logs from the archive.
                                                      cap_sizes[c])
     return 0
 
-def find_tapdisk_logs():
-    return glob.glob('/var/log/blktap/*.log*')
-
-def generate_tapdisk_logs():
-    for pid in pidof('tapdisk'):
-       try:
-           os.kill(pid, SIGUSR1)
-            output_ts("Including logs for tapdisk process %d" % pid)
-        except :
-            pass
-    # give processes a second to write their logs
-    time.sleep(1)
-
-def clean_tapdisk_logs():
-    for filename in find_tapdisk_logs():
-        try:
-            os.remove(filename)
-        except :
-            pass
-
-def filter_db_pii(str, state):
-    if 'in_secret_table' not in state:
-        state['in_secret_table'] = False
-
-    if str.startswith('<table ') and 'name="secret"' in str:
-        state['in_secret_table'] = True
-    elif str.startswith('</table>'):
-        state['in_secret_table'] = False
-
-    if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
-        str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
-    return str
-
 def dump_scsi_hosts(cap):
     output = ''
     l = os.listdir('/sys/class/scsi_host')
@@ -934,8 +854,12 @@ def load_plugins(just_capabilities=False, filter=None):
                     continue
                 if el.tagName == "files":
                     newest_first = getBoolAttr(el, 'newest_first')
-                    file_output(dir, getText(el.childNodes).split(),
-                                newest_first=newest_first)
+                    if el.getAttribute("type") == "logs":
+                        log_output(dir, getText(el.childNodes).split(),
+                                    newest_first=newest_first)
+                    else:
+                        file_output(dir, getText(el.childNodes).split(),
+                                    newest_first=newest_first)
                 elif el.tagName == "directory":
                     pattern = el.getAttribute("pattern")
                     if pattern == '': pattern = None
@@ -947,7 +871,8 @@ def load_plugins(just_capabilities=False, filter=None):
                 elif el.tagName == "command":
                     label = el.getAttribute("label")
                     if label == '': label = None
-                    cmd_output(dir, getText(el.childNodes), label)
+                    binary = getBoolAttr(el, 'binary')
+                    cmd_output(dir, getText(el.childNodes), label, binary=binary)
 
 def make_tar(subdir, suffix, output_fd, output_file):
     global SILENT_MODE, data
@@ -1185,7 +1110,7 @@ def disk_list():
 class ProcOutput:
     debug = False
 
-    def __init__(self, command, max_time, inst=None, filter=None):
+    def __init__(self, command, max_time, inst=None, filter=None, binary=False):
         self.command = command
         self.max_time = max_time
         self.inst = inst
@@ -1196,6 +1121,10 @@ class ProcOutput:
         self.timeout = int(time.time()) + self.max_time
         self.filter = filter
         self.filter_state = {}
+        if binary:
+            self.bufsize = 1048576  # 1MB buffer
+        else:
+            self.bufsize = 1        # line buffered
 
     def __del__(self):
         self.terminate()
@@ -1208,7 +1137,9 @@ class ProcOutput:
         try:
             if ProcOutput.debug:
                 output_ts("Starting '%s'" % self.cmdAsStr())
-            self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
+            self.proc = Popen(self.command, bufsize=self.bufsize,
+                              stdin=dev_null, stdout=PIPE, stderr=dev_null,
+                              shell=isinstance(self.command, str))
             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
             self.running = True
@@ -1231,7 +1162,10 @@ class ProcOutput:
 
     def read_line(self):
         assert self.running
-        line = self.proc.stdout.readline()
+        if self.bufsize == 1:
+            line = self.proc.stdout.readline()
+        else:
+            line = self.proc.stdout.read(self.bufsize)
         if line == '':
             # process exited
             self.proc.stdout.close()