1# Copyright (c) 2013 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 cStringIO, collections, dbus, gzip, logging, subprocess 6 7from autotest_lib.client.bin import test 8from autotest_lib.client.common_lib import error 9 10 11class platform_DebugDaemonGetPerfData(test.test): 12 """ 13 This autotest tests the collection of perf data. It calls perf indirectly 14 through debugd -> quipper -> perf. 15 16 The perf data is collected both when the system is idle and when there is a 17 process running in the background. 18 19 The perf data is collected over various durations. 20 """ 21 22 version = 1 23 24 # A list of durations over which to gather perf data using quipper (given in 25 # seconds), plus the number of times to run perf with each duration. 26 # e.g. the entry "1: 50" means to run perf for 1 second 50 times. 27 _profile_duration_and_repetitions = [ 28 (1, 3), 29 (5, 1) 30 ] 31 32 # Commands to repeatedly run in the background when collecting perf data. 33 _system_load_commands = { 34 'idle' : 'sleep 1', 35 'busy' : 'ls', 36 } 37 38 _dbus_debugd_object = '/org/chromium/debugd' 39 _dbus_debugd_name = 'org.chromium.debugd' 40 41 # For storing the size of returned results. 42 SizeInfo = collections.namedtuple('SizeInfo', ['size', 'size_zipped']) 43 44 def gzip_string(self, string): 45 """ 46 Gzip a string. 47 48 @param string: The input string to be gzipped. 49 50 Returns: 51 The gzipped string. 52 """ 53 string_file = cStringIO.StringIO() 54 gzip_file = gzip.GzipFile(fileobj=string_file, mode='wb') 55 gzip_file.write(string) 56 gzip_file.close() 57 return string_file.getvalue() 58 59 60 def validate_get_perf_method(self, duration, num_reps, load_type): 61 """ 62 Validate a debugd method that returns perf data. 63 64 @param duration: The duration to use for perf data collection. 65 @param num_reps: Number of times to run. 66 @param load_type: A label to use for storing into perf keyvals. 67 """ 68 # Dictionary for storing results returned from debugd. 69 # Key: Name of data type (string) 70 # Value: Sizes of results in bytes (list of SizeInfos) 71 stored_results = collections.defaultdict(list) 72 73 for _ in range(num_reps): 74 perf_command = ['perf', 'record', '-a', '-e', 'cycles', 75 '-c', '1000003'] 76 status, perf_data, perf_stat = self.dbus_iface.GetPerfOutput( 77 duration, perf_command, signature="uas") 78 if status != 0: 79 raise error.TestFail('GetPerfOutput() returned status %d', 80 status) 81 if len(perf_data) == 0 and len(perf_stat) == 0: 82 raise error.TestFail('GetPerfOutput() returned no data') 83 if len(perf_data) > 0 and len(perf_stat) > 0: 84 raise error.TestFail('GetPerfOutput() returned both ' 85 'perf_data and perf_stat') 86 87 result_type = None 88 if perf_data: 89 result = perf_data 90 result_type = "perf_data" 91 else: # if perf_stat 92 result = perf_stat 93 result_type = "perf_stat" 94 95 logging.info('GetPerfOutput() for %s seconds returned %d ' 96 'bytes of type %s', 97 duration, len(result), result_type) 98 if len(result) < 10: 99 raise error.TestFail('Perf output too small') 100 101 # Convert |result| from an array of dbus.Bytes to a string. 102 result = ''.join(chr(b) for b in result) 103 104 # If there was an error in collecting a profile with quipper, debugd 105 # will output an error message. Make sure to check for this message. 106 # It is found in PerfTool::GetPerfDataHelper() in 107 # debugd/src/perf_tool.cc. 108 if result.startswith('<process exited with status: '): 109 raise error.TestFail('Quipper failed: %s' % result) 110 111 stored_results[result_type].append( 112 self.SizeInfo(len(result), len(self.gzip_string(result)))) 113 114 for result_type, sizes in stored_results.iteritems(): 115 key = 'mean_%s_size_%s_%d' % (result_type, load_type, duration) 116 total_size = sum(entry.size for entry in sizes) 117 total_size_zipped = sum(entry.size_zipped for entry in sizes) 118 119 keyvals = {} 120 keyvals[key] = total_size / len(sizes) 121 keyvals[key + '_zipped'] = total_size_zipped / len(sizes) 122 self.write_perf_keyval(keyvals) 123 124 125 def run_once(self, *args, **kwargs): 126 """ 127 Primary autotest function. 128 """ 129 130 bus = dbus.SystemBus() 131 proxy = bus.get_object( 132 self._dbus_debugd_name, self._dbus_debugd_object, introspect=False) 133 self.dbus_iface = dbus.Interface(proxy, 134 dbus_interface=self._dbus_debugd_name) 135 136 # Open /dev/null to redirect unnecessary output. 137 devnull = open('/dev/null', 'w') 138 139 load_items = self._system_load_commands.iteritems() 140 for load_type, load_command in load_items: 141 # Repeatedly run the comand for the current load. 142 cmd = 'while true; do %s; done' % load_command 143 process = subprocess.Popen(cmd, stdout=devnull, shell=True) 144 145 for duration, num_reps in self._profile_duration_and_repetitions: 146 # Collect perf data from debugd. 147 self.validate_get_perf_method(duration, num_reps, load_type) 148 149 # Terminate the process and actually wait for it to terminate. 150 process.terminate() 151 while process.poll() == None: 152 pass 153 154 devnull.close() 155