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 5import base64, dbus, json, logging, os 6from subprocess import Popen, PIPE 7from threading import Thread 8 9from autotest_lib.client.bin import test 10from autotest_lib.client.common_lib import error 11from autotest_lib.client.cros import debugd_util 12 13class 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 28class 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