Home | History | Annotate | Download | only in platform_DebugDaemonGetPerfOutputFd
      1 # Copyright 2018 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import dbus, os, time
      6 
      7 from autotest_lib.client.bin import test
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.cros import debugd_util
     10 
     11 class platform_DebugDaemonGetPerfOutputFd(test.test):
     12     """
     13     This autotest tests the collection of perf data.  It calls perf indirectly
     14     through debugd -> quipper -> perf. This also tests stopping the perf
     15     session.
     16 
     17     The perf data is read from a pipe that is redirected to stdout of the
     18     quipper process.
     19     """
     20 
     21     version = 1
     22 
     23     def check_perf_output(self, perf_data):
     24         """
     25         Utility function to validate the perf data that was previously read
     26         from the pipe.
     27         """
     28         if len(perf_data) < 10:
     29             raise error.TestFail('Perf output (%s) too small' % perf_data)
     30 
     31         # Perform basic sanity checks of the perf data: it should contain
     32         # [kernel.kallsyms] and /usr/bin/perf
     33         if (perf_data.find('[kernel.kallsyms]') == -1 or
     34                 perf_data.find('/usr/bin/perf') == -1):
     35             raise error.TestFail('Quipper failed: %s' % perf_data)
     36 
     37     def call_get_perf_output_fd(self, duration):
     38         """
     39         Utility function to call DBus method GetPerfOutputFd with the given
     40         duration.
     41         """
     42         pipe_r, pipe_w = os.pipe()
     43         perf_command = ['perf', 'record', '-a', '-F', '100']
     44 
     45         session_id = self.dbus_iface.GetPerfOutputFd(
     46             duration, perf_command, dbus.types.UnixFd(pipe_w), signature="uash")
     47 
     48         # pipe_w is dup()'d in calling dbus. Close in this process.
     49         os.close(pipe_w)
     50 
     51         if session_id == 0:
     52             raise error.TestFail('Invalid session ID from GetPerfOutputFd')
     53 
     54         # Don't explicitly os.close(pipe_r) since it will be closed
     55         # automatically when the file object returned by os.fdopen() is closed.
     56         return session_id, os.fdopen(pipe_r, 'r')
     57 
     58     def call_stop_perf(self, session_id, real_duration):
     59         """
     60         Utility function to call DBus method StopPerf to collect perf data
     61         collected within the given duration.
     62         """
     63         # Sleep for real_duration seconds and then stop the perf session.
     64         time.sleep(real_duration)
     65         self.dbus_iface.StopPerf(session_id, signature='t')
     66 
     67     def test_full_duration(self):
     68         """
     69         Test GetPerfOutpuFd to collect a profile of 2 seconds.
     70         """
     71 
     72         session_id, result_file = self.call_get_perf_output_fd(2)
     73 
     74         # This performs synchronous read until perf exits.
     75         result = result_file.read()
     76 
     77         self.check_perf_output(result)
     78 
     79     def test_stop_perf(self):
     80         """
     81         Test StopPerf by calling GetPerfOutputFd to collect a profile of 30
     82         seconds. After the perf session is started for 2 seconds, call StopPerf
     83         to stop the profiling session. The net result is a profile of 2
     84         seconds. Verify StopPerf working by timing the test case: the test case
     85         shouldn't run for 30 seconds or longer.
     86         """
     87         start = time.time()
     88 
     89         # Default duration is 30 sec.
     90         session_id, result_file = self.call_get_perf_output_fd(30)
     91 
     92         # Get a profile of 2 seconds by premature stop.
     93         self.call_stop_perf(session_id, 2)
     94 
     95         # This performs synchronous read until perf exits.
     96         result = result_file.read()
     97 
     98         self.check_perf_output(result)
     99 
    100         end = time.time()
    101         if (end - start) >= 30:
    102             raise error.TestFail('Unable to stop the perf tool')
    103 
    104     def test_start_after_previous_finished(self):
    105         """
    106         Test consecutive GetPerfOutputFd calls that there is no undesirable
    107         side effect left in the previous profiling session.
    108         """
    109         self.test_full_duration()
    110         self.test_full_duration()
    111 
    112     def test_stop_without_start(self):
    113         """
    114         Test unmatched StopPerf call by checking the returned DBusException.
    115         """
    116         dbus_message = None
    117         try:
    118             self.call_stop_perf(0, 1)
    119         except dbus.exceptions.DBusException as dbus_exception:
    120             dbus_message = dbus_exception.get_dbus_message()
    121 
    122         if dbus_message is None:
    123             raise error.TestFail('DBusException expected')
    124         if dbus_message.find('Perf tool not started') == -1:
    125             raise error.TestFail('Unexpected DBus message: %s' % dbus_message)
    126 
    127     def test_stop_using_wrong_id(self):
    128         """
    129         Test calling StopPerf with an invalid session ID by checking the
    130         returned DBusException.
    131         """
    132         start = time.time()
    133 
    134         # Default duration is 30 sec.
    135         session_id, result_file = self.call_get_perf_output_fd(30)
    136 
    137         dbus_message = None
    138         try:
    139             # Use session_id - 1 to trigger the error condition.
    140             self.call_stop_perf(session_id - 1, 1)
    141         except dbus.exceptions.DBusException as dbus_exception:
    142             dbus_message = dbus_exception.get_dbus_message()
    143 
    144         if dbus_message is None:
    145             raise error.TestFail('DBusException expected')
    146         if dbus_message.find('Invalid profile session id') == -1:
    147             raise error.TestFail('Unexpected DBus message: %s' % dbus_message)
    148 
    149         # Get a profile of 1 second by premature stop.
    150         self.call_stop_perf(session_id, 1)
    151 
    152         # This performs synchronous read until perf exits.
    153         result = result_file.read()
    154 
    155         self.check_perf_output(result)
    156 
    157         end = time.time()
    158         if (end - start) >= 30:
    159             raise error.TestFail('Unable to stop the perf tool')
    160 
    161     def test_start_2nd_time(self):
    162         """
    163         Test calling GetPerfOutputFd when an existing profiling session is
    164         running: the 2nd call should yield a DBusException without affecting
    165         the 1st call.
    166         """
    167         # Default duration is 30 sec.
    168         session_id, result_file = self.call_get_perf_output_fd(30)
    169 
    170         dbus_message = None
    171         try:
    172             self.call_get_perf_output_fd(60)
    173         except dbus.exceptions.DBusException as dbus_exception:
    174             dbus_message = dbus_exception.get_dbus_message()
    175 
    176         if dbus_message is None:
    177             raise error.TestFail('DBusException expected')
    178         if dbus_message.find('Existing perf tool running') == -1:
    179             raise error.TestFail('Unexpected DBus message: %s' % dbus_message)
    180 
    181         # Get a profile of 1 second by premature stop.
    182         self.call_stop_perf(session_id, 1)
    183 
    184         # This performs synchronous read until perf exits.
    185         result = result_file.read()
    186 
    187         self.check_perf_output(result)
    188 
    189     def run_once(self, *args, **kwargs):
    190         """
    191         Primary autotest function.
    192         """
    193         # Setup.
    194         self.dbus_iface = debugd_util.iface()
    195 
    196         # Test normal cases.
    197         self.test_full_duration()
    198         self.test_start_after_previous_finished()
    199         self.test_stop_perf()
    200 
    201         # Test error cases.
    202         self.test_stop_without_start()
    203         self.test_stop_using_wrong_id()
    204         self.test_start_2nd_time()
    205