Home | History | Annotate | Download | only in platform_DebugDaemonGetPerfData
      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 
      5 import cStringIO, collections, dbus, gzip, logging, subprocess
      6 
      7 from autotest_lib.client.bin import test
      8 from autotest_lib.client.common_lib import error
      9 
     10 
     11 class 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