[Resubmit] Refactor packet capture to use new logging library.

Use the acts.libs.proc.process library to create a process that logs tcpdump output to local files.

Bug: 120086380

Test: act.py -c <config> -tc WifiNetworkSelectorTest -tb chromeos1-dev-test-station-3
Test: wifi interop tests

Change-Id: I55701e645e7fc903312a795616de16389cf17702
diff --git a/acts/framework/acts/controllers/packet_capture.py b/acts/framework/acts/controllers/packet_capture.py
index 3947bda..caf70b7 100755
--- a/acts/framework/acts/controllers/packet_capture.py
+++ b/acts/framework/acts/controllers/packet_capture.py
@@ -18,8 +18,12 @@
 from acts.controllers.ap_lib.hostapd_constants import AP_DEFAULT_CHANNEL_2G
 from acts.controllers.ap_lib.hostapd_constants import AP_DEFAULT_CHANNEL_5G
 from acts.controllers.utils_lib.ssh import connection
+from acts.controllers.utils_lib.ssh import formatter
 from acts.controllers.utils_lib.ssh import settings
+from acts.libs.logging import log_stream
+from acts.libs.proc.process import Process
 
+import logging
 import os
 import threading
 import time
@@ -44,10 +48,12 @@
 def create(configs):
     return [PacketCapture(c) for c in configs]
 
+
 def destroy(pcaps):
     for pcap in pcaps:
         pcap.close()
 
+
 def get_info(pcaps):
     return [pcap.ssh_settings.hostname for pcap in pcaps]
 
@@ -56,17 +62,13 @@
     """Class to maintain packet capture properties after starting tcpdump.
 
     Attributes:
-        pid: proccess id of tcpdump
-        pcap_dir: tmp dir location where pcap files are saved
+        proc: Process object of tcpdump
         pcap_file: pcap file name
-        pcap_thread: thread used to push files to logpath
     """
-    def __init__(self, pid, pcap_dir, pcap_file, pcap_thread):
+    def __init__(self, proc, pcap_file):
         """Initialize object."""
-        self.pid = pid
-        self.pcap_dir = pcap_dir
+        self.proc = proc
         self.pcap_file = pcap_file
-        self.pcap_thread = pcap_thread
 
 
 class PacketCaptureError(Exception):
@@ -81,8 +83,8 @@
     wifi networks; 'wlan2' which is a dual band interface.
 
     Attributes:
-        pcap: dict that specifies packet capture properties for a band.
-        tmp_dirs: list of tmp directories created for pcap files.
+        pcap_properties: dict that specifies packet capture properties for a
+            band.
     """
     def __init__(self, configs):
         """Initialize objects.
@@ -101,7 +103,6 @@
 
         self.pcap_properties = dict()
         self._pcap_stop_lock = threading.Lock()
-        self.tmp_dirs = []
 
     def _create_interface(self, iface, mode):
         """Create interface of monitor/managed mode.
@@ -157,62 +158,6 @@
                 network = {}
         return scan_networks
 
-    def _check_if_tcpdump_started(self, pcap_log):
-        """Check if tcpdump started.
-
-        This method ensures that tcpdump has started successfully.
-        We look for 'listening on' from the stdout indicating that tcpdump
-        is started.
-
-        Args:
-            pcap_log: log file that has redirected output of starting tcpdump.
-
-        Returns:
-            True/False if tcpdump is started or not.
-        """
-        curr_time = time.time()
-        timeout = 3
-        find_str = 'listening on'
-        while time.time() < curr_time + timeout:
-            result = self.ssh.run('grep "%s" %s' % (find_str, pcap_log),
-                                  ignore_status=True)
-            if result.stdout and find_str in result.stdout:
-                return True
-            time.sleep(1)
-        return False
-
-    def _pull_pcap(self, band, pcap_file, log_path):
-        """Pulls pcap files to test log path from onhub.
-
-        Called by start_packet_capture(). This method moves a pcap file to log
-        path once it has reached 50MB.
-
-        Args:
-            index: param that indicates if the tcpdump is stopped.
-            pcap_file: pcap file to move.
-            log_path: log path to move the pcap file to.
-        """
-        curr_no = 0
-        while True:
-            next_no = curr_no + 1
-            curr_fno = '%02i' % curr_no
-            next_fno = '%02i' % next_no
-            curr_file = '%s%s' % (pcap_file, curr_fno)
-            next_file = '%s%s' % (pcap_file, next_fno)
-
-            result = self.ssh.run('ls %s' % next_file, ignore_status=True)
-            if not result.stderr and next_file in result.stdout:
-                self.ssh.pull_file(log_path, curr_file)
-                self.ssh.run('rm -rf %s' % curr_file, ignore_status=True)
-                curr_no += 1
-                continue
-
-            with self._pcap_stop_lock:
-                if band not in self.pcap_properties:
-                    self.ssh.pull_file(log_path, curr_file)
-                    break
-            time.sleep(2) # wait before looking for file again
-
     def get_wifi_scan_results(self):
         """Starts a wifi scan on wlan2 interface.
 
@@ -275,66 +220,53 @@
         band = 2G starts tcpdump on 'mon0' interface.
         band = 5G starts tcpdump on 'mon1' interface.
 
-        This method splits the pcap file every 50MB for 100 files.
-        Since, the size of the pcap file could become large, each split file
-        is moved to log_path once a new file is generated. This ensures that
-        there is no crash on the onhub router due to lack of space.
-
         Args:
             band: '2g' or '2G' and '5g' or '5G'.
             log_path: test log path to save the pcap file.
             pcap_file: name of the pcap file.
 
         Returns:
-            pid: process id of the tcpdump.
+            pcap_proc: Process object of the tcpdump.
         """
         band = band.upper()
         if band not in BAND_IFACE.keys() or band in self.pcap_properties:
             self.log.error("Invalid band or packet capture already running")
             return None
 
-        pcap_dir = self.ssh.run('mktemp -d', ignore_status=True).stdout.rstrip()
-        self.tmp_dirs.append(pcap_dir)
-        pcap_file = os.path.join(pcap_dir, "%s_%s.pcap" % (pcap_file, band))
-        pcap_log = os.path.join(pcap_dir, "%s.log" % pcap_file)
+        pcap_name = "%s_pcap" % band
+        pcap_file = os.path.join(log_path, pcap_name)
 
-        cmd = 'tcpdump -i %s -W 100 -C 50 -w %s > %s 2>&1 & echo $!' % (
-            BAND_IFACE[band], pcap_file, pcap_log)
-        result = self.ssh.run(cmd, ignore_status=True)
-        if not self._check_if_tcpdump_started(pcap_log):
-            self.log.error("Failed to start packet capture")
-            return None
+        pcap_logger = log_stream.create_logger(
+            pcap_name, base_path=log_path,
+            log_styles=(log_stream.LogStyles.LOG_DEBUG +
+                        log_stream.LogStyles.MONOLITH_LOG))
+        pcap_logger.setLevel(logging.DEBUG)
+        cmd = formatter.SshFormatter().format_command(
+            'tcpdump -i %s -l' %
+            (BAND_IFACE[band]), None, self.ssh_settings)
+        pcap_proc = Process(cmd)
+        pcap_proc.set_on_output_callback(lambda msg: pcap_logger.debug(msg))
+        pcap_proc.start()
 
-        pcap_thread = threading.Thread(target=self._pull_pcap,
-                                       args=(band, pcap_file, log_path))
-        pcap_thread.start()
+        self.pcap_properties[band] = PcapProperties(pcap_proc, pcap_file)
+        return pcap_proc
 
-        pid = int(result.stdout)
-        self.pcap_properties[band] = PcapProperties(
-            pid, pcap_dir, pcap_file, pcap_thread)
-        return pid
-
-    def stop_packet_capture(self, pid):
+    def stop_packet_capture(self, proc):
         """Stop the packet capture.
 
         Args:
-            pid: process id of tcpdump to kill.
+            proc: Process object of tcpdump to kill.
         """
         for key, val in self.pcap_properties.items():
-            if val.pid == pid:
+            if val.proc is proc:
                 break
         else:
-            self.log.error("Failed to stop tcpdump. Invalid PID %s" % pid)
+            self.log.error("Failed to stop tcpdump. Invalid process.")
             return
 
-        pcap_dir = val.pcap_dir
-        pcap_thread = val.pcap_thread
-        self.ssh.run('kill %s' % pid, ignore_status=True)
+        proc.stop()
         with self._pcap_stop_lock:
             del self.pcap_properties[key]
-        pcap_thread.join()
-        self.ssh.run('rm -rf %s' % pcap_dir, ignore_status=True)
-        self.tmp_dirs.remove(pcap_dir)
 
     def close(self):
         """Cleanup.
@@ -343,6 +275,4 @@
         """
         self._cleanup_interface(MON_2G)
         self._cleanup_interface(MON_5G)
-        for tmp_dir in self.tmp_dirs:
-            self.ssh.run('rm -rf %s' % tmp_dir, ignore_status=True)
         self.ssh.close()
diff --git a/acts/framework/acts/test_utils/wifi/wifi_test_utils.py b/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
index 67bac00..433fdab 100755
--- a/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
@@ -16,7 +16,7 @@
 
 import logging
 import os
-import pprint
+import shutil
 import time
 
 from enum import IntEnum
@@ -1710,8 +1710,8 @@
         test_name: test name to be used for pcap file name
 
     Returns:
-        Dictionary with pid of the tcpdump process as key and log path
-        of the file name as the value
+        Dictionary with wifi band as key and the tuple
+        (pcap Process object, log directory) as the value
     """
     log_dir = os.path.join(log_path, test_name)
     utils.create_dir(log_dir)
@@ -1719,13 +1719,13 @@
         bands = [BAND_2G, BAND_5G]
     else:
         bands = [wifi_band]
-    pids = {}
+    procs = {}
     for band in bands:
-        pid = pcap.start_packet_capture(band, log_dir, test_name)
-        pids[pid] = os.path.join(log_dir, test_name)
-    return pids
+        proc = pcap.start_packet_capture(band, log_dir, test_name)
+        procs[band] = (proc, os.path.join(log_dir, test_name))
+    return procs
 
-def stop_pcap(pcap, pids, test_status=None):
+def stop_pcap(pcap, procs, test_status=None):
     """Stop packet capture in monitor mode.
 
     Since, the pcap logs in monitor mode can be very large, we will
@@ -1734,14 +1734,14 @@
 
     Args:
         pcap: packet capture object
-        pids: dictionary returned by start_pcap
+        procs: dictionary returned by start_pcap
         test_status: status of the test case
     """
-    for pid, fname in pids.items():
-        pcap.stop_packet_capture(pid)
+    for proc, fname in procs.values():
+        pcap.stop_packet_capture(proc)
 
     if test_status:
-        os.system('rm -rf %s' % os.path.dirname(fname))
+        shutil.rmtree(os.path.dirname(fname))
 
 def start_cnss_diags(ads):
     for ad in ads:
diff --git a/acts/tests/google/wifi/WifiChaosTest.py b/acts/tests/google/wifi/WifiChaosTest.py
index 68dc55a..8a66dd4 100755
--- a/acts/tests/google/wifi/WifiChaosTest.py
+++ b/acts/tests/google/wifi/WifiChaosTest.py
@@ -122,10 +122,10 @@
         self.dut.droid.wakeUpNow()
 
     def on_pass(self, test_name, begin_time):
-        wutils.stop_pcap(self.pcap, self.pcap_pid, True)
+        wutils.stop_pcap(self.pcap, self.pcap_procs, True)
 
     def on_fail(self, test_name, begin_time):
-        wutils.stop_pcap(self.pcap, self.pcap_pid, False)
+        wutils.stop_pcap(self.pcap, self.pcap_procs, False)
         self.dut.take_bug_report(test_name, begin_time)
         self.dut.cat_adb_log(test_name, begin_time)
 
@@ -278,7 +278,7 @@
 
         self.get_band_and_chan(ssid)
         self.pcap.configure_monitor_mode(self.band, self.chan)
-        self.pcap_pid = wutils.start_pcap(
+        self.pcap_procs = wutils.start_pcap(
                 self.pcap, self.band.lower(), self.log_path, self.test_name)
         self.run_connect_disconnect(network, hostname, rpm_port, rpm_ip,
                                     release_ap)
diff --git a/acts/tests/google/wifi/WifiNetworkSelectorTest.py b/acts/tests/google/wifi/WifiNetworkSelectorTest.py
index ffeb6b5..7015636 100644
--- a/acts/tests/google/wifi/WifiNetworkSelectorTest.py
+++ b/acts/tests/google/wifi/WifiNetworkSelectorTest.py
@@ -74,7 +74,7 @@
         self.dut.ed.clear_all_events()
 
         if hasattr(self, 'packet_capture'):
-            self.pcap_pids = wutils.start_pcap(
+            self.pcap_procs = wutils.start_pcap(
                 self.packet_capture, 'dual', self.log_path, self.test_name)
 
     def teardown_test(self):
@@ -84,11 +84,11 @@
 
     def on_pass(self, test_name, begin_time):
         if hasattr(self, 'packet_capture'):
-            wutils.stop_pcap(self.packet_capture, self.pcap_pids, True)
+            wutils.stop_pcap(self.packet_capture, self.pcap_procs, True)
 
     def on_fail(self, test_name, begin_time):
         if hasattr(self, 'packet_capture'):
-            wutils.stop_pcap(self.packet_capture, self.pcap_pids, False)
+            wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
         self.dut.take_bug_report(test_name, begin_time)
         self.dut.cat_adb_log(test_name, begin_time)