bluetooth: add btmon log for raspberry pi and DUT

- Capture the btmon logs in both raspberry pi and DUT.
- Modify the cleanup logic in bluetooth_adapter_tests.py.
  The cleanup function could be called in the end of the testing,
  between the subtests, or in the middle of a test, each of them
  has different needs.

BUG=b:144925225
TEST=run bluetooth_AdapterLESanity.le_connect_disconnect_loop
     run bluetooth_AdapterLESanity
     run bluetooth_AdapterQSSanity.stress

     Also run quick sanity with Fizz device.
Change-Id: If81c3158e55fc06b46c1facb418fa14f8a8880d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/1981656
Reviewed-by: Daniel Winkler <danielwinkler@google.com>
Tested-by: Yun-Hao Chung <howardchung@google.com>
Commit-Queue: Yun-Hao Chung <howardchung@google.com>
diff --git a/client/cros/bluetooth/bluetooth_device_xmlrpc_server.py b/client/cros/bluetooth/bluetooth_device_xmlrpc_server.py
index 3270abf..b30bc48 100755
--- a/client/cros/bluetooth/bluetooth_device_xmlrpc_server.py
+++ b/client/cros/bluetooth/bluetooth_device_xmlrpc_server.py
@@ -116,6 +116,7 @@
 
     BLUETOOTH_LIBDIR = '/var/lib/bluetooth'
     BTMON_STOP_DELAY_SECS = 3
+    BTMON_LOG_PATH = '/var/log/btsnoop.log'
 
     # Timeout for how long we'll wait for BlueZ and the Adapter to show up
     # after reset.
@@ -168,7 +169,8 @@
 
         # Initailize a btmon object to record bluetoothd's activity.
         self.btmon = output_recorder.OutputRecorder(
-                'btmon', stop_delay_secs=self.BTMON_STOP_DELAY_SECS)
+                ('btmon -SAw %s' % self.BTMON_LOG_PATH).split(' '),
+                stop_delay_secs=self.BTMON_STOP_DELAY_SECS)
 
         self.advertisements = []
         self._adv_mainloop = gobject.MainLoop()
diff --git a/client/cros/bluetooth/output_recorder.py b/client/cros/bluetooth/output_recorder.py
index f13c6b0..adbe883 100644
--- a/client/cros/bluetooth/output_recorder.py
+++ b/client/cros/bluetooth/output_recorder.py
@@ -105,7 +105,8 @@
                     # special unicode such that we would like to escape.
                     # In this way, regular expression search could be conducted
                     # properly.
-                    self.contents.append(line.encode('unicode-escape'))
+                    line = line.decode(errors='ignore').encode('unicode-escape')
+                    self.contents.append(line)
                 elif self._stop_recording_thread_event.is_set():
                     self._stop_recording_thread_event.clear()
                     break
diff --git a/client/cros/chameleon/chameleon.py b/client/cros/chameleon/chameleon.py
index 4f14c8c..0921e04 100644
--- a/client/cros/chameleon/chameleon.py
+++ b/client/cros/chameleon/chameleon.py
@@ -27,6 +27,7 @@
 CHAMELEON_PORT = 9992
 CHAMELEOND_LOG_REMOTE_PATH = '/var/log/chameleond'
 DAEMON_LOG_REMOTE_PATH = '/var/log/daemon.log'
+BTMON_LOG_REMOTE_PATH = '/var/log/btsnoop.log'
 CHAMELEON_READY_TEST = 'GetSupportedPorts'
 
 
@@ -320,6 +321,42 @@
                 atexit.register(log_new_func)
 
 
+        def btmon_atexit_gen(btmon_pid):
+            """Generate a function to kill the btmon process and save the log
+
+            @param btmon_pid: PID of the btmon process
+            """
+
+            def btmon_atexit():
+                """Kill the btmon with specified PID and save the log"""
+
+                file_name = os.path.basename(BTMON_LOG_REMOTE_PATH)
+                target_path = os.path.join(log_dir, file_name)
+
+                self.host.run('kill %d' % btmon_pid)
+                self.host.get_file(BTMON_LOG_REMOTE_PATH, target_path)
+            return btmon_atexit
+
+
+        # Kill all btmon process before creating a new one
+        self.host.run('pkill btmon || true')
+
+        # Get available btmon options in the chameleon host
+        btmon_options = ''
+        btmon_help = self.host.run('btmon --help').stdout
+
+        for option in 'SA':
+            if '-%s' % option in btmon_help:
+                btmon_options += option
+
+        # Store btmon log
+        btmon_pid = int(self.host.run_background('btmon -%sw %s'
+                                                % (btmon_options,
+                                                BTMON_LOG_REMOTE_PATH)))
+        if btmon_pid > 0:
+            atexit.register(btmon_atexit_gen(btmon_pid))
+
+
     def reboot(self):
         """Reboots Chameleon board."""
         self._chameleond_proxy.Reboot()
diff --git a/server/cros/bluetooth/bluetooth_adapter_quick_tests.py b/server/cros/bluetooth/bluetooth_adapter_quick_tests.py
index e980ba0..885e75c 100644
--- a/server/cros/bluetooth/bluetooth_adapter_quick_tests.py
+++ b/server/cros/bluetooth/bluetooth_adapter_quick_tests.py
@@ -56,7 +56,7 @@
 
         # Grab currect device list for initialization
         connected_devices = self.devices
-        self.cleanup(on_start=False)
+        self.cleanup(test_state='MID')
 
         for device_type, device_list in connected_devices.items():
             for device in device_list:
@@ -113,6 +113,10 @@
 
         self.enable_disable_debug_log(enable=True)
 
+        # Kill all btmon process before creating a new one
+        self.bluetooth_facade.host.run('pkill btmon || true')
+        self.bluetooth_facade.btmon_start()
+
         self.flag = flag
         self.test_iter = None
 
@@ -273,7 +277,7 @@
             self.group_chameleons_type()
 
         # Close the connection between peers
-        self.cleanup()
+        self.cleanup(test_state='NEW')
 
 
     @staticmethod
diff --git a/server/cros/bluetooth/bluetooth_adapter_tests.py b/server/cros/bluetooth/bluetooth_adapter_tests.py
index 46d8af7..1e91f29 100644
--- a/server/cros/bluetooth/bluetooth_adapter_tests.py
+++ b/server/cros/bluetooth/bluetooth_adapter_tests.py
@@ -2877,10 +2877,19 @@
         raise NotImplementedError
 
 
-    def cleanup(self, on_start=True):
-        """Clean up bluetooth adapter tests."""
-        # Disable all the bluetooth debug logs
-        self.enable_disable_debug_log(enable=False)
+    def cleanup(self, test_state='END'):
+        """Clean up bluetooth adapter tests.
+
+        @param test_state: string describing the requested clear is for
+                           a new test(NEW), the middle of the test(MID),
+                           or the end of the test(END).
+        """
+
+        if test_state is 'END':
+            # Disable all the bluetooth debug logs
+            self.enable_disable_debug_log(enable=False)
+            # Stop btmon process
+            self.bluetooth_facade.btmon_stop()
 
         # Close the device properly if a device is instantiated.
         # Note: do not write something like the following statements
@@ -2895,7 +2904,7 @@
                     device.Close()
 
                     # Power cycle BT device if we're in the middle of a test
-                    if not on_start:
+                    if test_state is 'MID':
                         device.PowerCycle()
 
         self.devices = dict()