• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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='hs', 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