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 5import dbus, os, time 6 7from autotest_lib.client.bin import test 8from autotest_lib.client.common_lib import error 9from autotest_lib.client.cros import debugd_util 10 11class 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