| # Copyright 2019 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import base64, dbus, json, logging, os |
| from subprocess import Popen, PIPE |
| from threading import Thread |
| |
| from autotest_lib.client.bin import test |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.cros import debugd_util |
| |
| class PipeReader(): |
| """ |
| The class to read from a pipe. Intended for running off the main thread. |
| """ |
| def __init__(self, pipe_r): |
| self.pipe_r = pipe_r |
| |
| def read(self): |
| """ |
| Drain from self.pipe_r and store the result in self.result. This method |
| runs in a new thread. |
| """ |
| # Read feedback logs content (JSON) from pipe_r. |
| self.result = os.fdopen(self.pipe_r, 'r').read() |
| |
| class platform_DebugDaemonPerfDataInFeedbackLogs(test.test): |
| """ |
| This autotest tests perf profile in feedback logs. It calls the debugd |
| method GetBigFeedbackLogs and checks whether 'perf-data' is present in the |
| returned logs. The perf data is base64-encoded lzma-compressed quipper |
| output. |
| """ |
| |
| version = 1 |
| |
| def xz_decompress_string(self, compressed_input): |
| """ |
| xz-decompresses a string. |
| |
| @param compressed_input: The input string to be decompressed. |
| |
| Returns: |
| The decompressed string. |
| """ |
| process = Popen('/usr/bin/xz -d', stdout=PIPE, stderr=PIPE, stdin=PIPE, |
| shell=True) |
| out, err = process.communicate(input=compressed_input) |
| |
| if len(err) > 0: |
| raise error.TestFail('decompress() failed with %s' % err) |
| |
| logging.info('decompress() %d -> %d bytes', len(compressed_input), |
| len(out)) |
| return out |
| |
| def validate_perf_data_in_feedback_logs(self): |
| """ |
| Validate that feedback logs contain valid perf data. |
| """ |
| pipe_r, pipe_w = os.pipe() |
| |
| # GetBigFeedbackReport transfers large content through the pipe. We |
| # need to read from the pipe off-thread to prevent a deadlock. |
| pipe_reader = PipeReader(pipe_r) |
| thread = Thread(target = pipe_reader.read) |
| thread.start() |
| |
| # Use 180-sec timeout because GetBigFeedbackLogs runs arc-bugreport, |
| # which takes a while to finish. |
| debugd_util.iface().GetBigFeedbackLogs(dbus.types.UnixFd(pipe_w), |
| signature='h', timeout=180) |
| |
| # pipe_w is dup()'d in calling dbus. Close in this process. |
| os.close(pipe_w) |
| thread.join() |
| |
| # Decode into a dictionary. |
| logs = json.loads(pipe_reader.result) |
| |
| if len(logs) == 0: |
| raise error.TestFail('GetBigFeedbackLogs() returned no data') |
| logging.info('GetBigFeedbackLogs() returned %d elements.', len(logs)) |
| |
| perf_data = logs['perf-data'] |
| |
| if perf_data is None: |
| raise error.TestFail('perf-data not found in feedback logs') |
| |
| BLOB_START_TOKEN = '<base64>: ' |
| try: |
| blob_start = perf_data.index(BLOB_START_TOKEN) |
| except: |
| raise error.TestFail(("perf-data doesn't include base64 encoded" |
| "data")) |
| |
| # Skip description text and BLOB_START_TOKEN |
| perf_data = perf_data[blob_start + len(BLOB_START_TOKEN):] |
| |
| logging.info('base64 perf data: %d bytes', len(perf_data)) |
| |
| # This raises TypeError if input is invalid base64-encoded data. |
| compressed_data = base64.b64decode(perf_data) |
| |
| protobuff = self.xz_decompress_string(compressed_data) |
| if len(protobuff) < 10: |
| raise error.TestFail('Perf output too small (%d bytes)' % |
| len(protobuff)) |
| |
| if protobuff.startswith('<process exited with status: '): |
| raise error.TestFail('Failed to capture a profile: %s' % |
| protobuff) |
| |
| def run_once(self, *args, **kwargs): |
| """ |
| Primary autotest function. |
| """ |
| self.validate_perf_data_in_feedback_logs() |
| |