Home | History | Annotate | Download | only in platform_DebugDaemonPerfDataInFeedbackLogs
      1 # Copyright 2019 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 base64, dbus, json, logging, os
      6 from subprocess import Popen, PIPE
      7 from threading import Thread
      8 
      9 from autotest_lib.client.bin import test
     10 from autotest_lib.client.common_lib import error
     11 from autotest_lib.client.cros import debugd_util
     12 
     13 class PipeReader():
     14     """
     15     The class to read from a pipe. Intended for running off the main thread.
     16     """
     17     def __init__(self, pipe_r):
     18         self.pipe_r = pipe_r
     19 
     20     def read(self):
     21         """
     22         Drain from self.pipe_r and store the result in self.result. This method
     23         runs in a new thread.
     24         """
     25         # Read feedback logs content (JSON) from pipe_r.
     26         self.result = os.fdopen(self.pipe_r, 'r').read()
     27 
     28 class platform_DebugDaemonPerfDataInFeedbackLogs(test.test):
     29     """
     30     This autotest tests perf profile in feedback logs. It calls the debugd
     31     method GetBigFeedbackLogs and checks whether 'perf-data' is present in the
     32     returned logs. The perf data is base64-encoded lzma-compressed quipper
     33     output.
     34     """
     35 
     36     version = 1
     37 
     38     def xz_decompress_string(self, compressed_input):
     39         """
     40         xz-decompresses a string.
     41 
     42         @param compressed_input: The input string to be decompressed.
     43 
     44         Returns:
     45           The decompressed string.
     46         """
     47         process = Popen('/usr/bin/xz -d', stdout=PIPE, stderr=PIPE, stdin=PIPE,
     48                         shell=True)
     49         out, err = process.communicate(input=compressed_input)
     50 
     51         if len(err) > 0:
     52             raise error.TestFail('decompress() failed with %s' % err)
     53 
     54         logging.info('decompress() %d -> %d bytes', len(compressed_input),
     55                      len(out))
     56         return out
     57 
     58     def validate_perf_data_in_feedback_logs(self):
     59         """
     60         Validate that feedback logs contain valid perf data.
     61         """
     62         pipe_r, pipe_w = os.pipe()
     63 
     64         # GetBigFeedbackReport transfers large content through the pipe. We
     65         # need to read from the pipe off-thread to prevent a deadlock.
     66         pipe_reader = PipeReader(pipe_r)
     67         thread = Thread(target = pipe_reader.read)
     68         thread.start()
     69 
     70         # Use 180-sec timeout because GetBigFeedbackLogs runs arc-bugreport,
     71         # which takes a while to finish.
     72         debugd_util.iface().GetBigFeedbackLogs(dbus.types.UnixFd(pipe_w),
     73                                                signature='h', timeout=180)
     74 
     75         # pipe_w is dup()'d in calling dbus. Close in this process.
     76         os.close(pipe_w)
     77         thread.join()
     78 
     79         # Decode into a dictionary.
     80         logs = json.loads(pipe_reader.result)
     81 
     82         if len(logs) == 0:
     83             raise error.TestFail('GetBigFeedbackLogs() returned no data')
     84         logging.info('GetBigFeedbackLogs() returned %d elements.', len(logs))
     85 
     86         perf_data = logs['perf-data']
     87 
     88         if perf_data is None:
     89             raise error.TestFail('perf-data not found in feedback logs')
     90 
     91         BLOB_START_TOKEN = '<base64>: '
     92         try:
     93             blob_start = perf_data.index(BLOB_START_TOKEN)
     94         except:
     95             raise error.TestFail(("perf-data doesn't include base64 encoded"
     96                                   "data"))
     97 
     98         # Skip description text and BLOB_START_TOKEN
     99         perf_data = perf_data[blob_start + len(BLOB_START_TOKEN):]
    100 
    101         logging.info('base64 perf data: %d bytes', len(perf_data))
    102 
    103         # This raises TypeError if input is invalid base64-encoded data.
    104         compressed_data = base64.b64decode(perf_data)
    105 
    106         protobuff = self.xz_decompress_string(compressed_data)
    107         if len(protobuff) < 10:
    108             raise error.TestFail('Perf output too small (%d bytes)' %
    109                                  len(protobuff))
    110 
    111         if protobuff.startswith('<process exited with status: '):
    112             raise error.TestFail('Failed to capture a profile: %s' %
    113                                  protobuff)
    114 
    115     def run_once(self, *args, **kwargs):
    116         """
    117         Primary autotest function.
    118         """
    119         self.validate_perf_data_in_feedback_logs()
    120 
    121