• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 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 dbus, os, time
6
7from autotest_lib.client.bin import test
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.cros import debugd_util
10
11class platform_DebugDaemonGetPerfOutputFd(test.test):
12    """
13    This autotest tests the collection of perf data.  It calls perf indirectly
14    through debugd -> quipper -> perf. This also tests stopping the perf
15    session.
16
17    The perf data is read from a pipe that is redirected to stdout of the
18    quipper process.
19    """
20
21    version = 1
22
23    def check_perf_output(self, perf_data):
24        """
25        Utility function to validate the perf data that was previously read
26        from the pipe.
27        """
28        if len(perf_data) < 10:
29            raise error.TestFail('Perf output (%s) too small' % perf_data)
30
31        # Perform basic sanity checks of the perf data: it should contain
32        # [kernel.kallsyms] and /usr/bin/perf
33        if (perf_data.find('[kernel.kallsyms]') == -1 or
34                perf_data.find('/usr/bin/perf') == -1):
35            raise error.TestFail('Quipper failed: %s' % perf_data)
36
37    def call_get_perf_output_fd(self, duration):
38        """
39        Utility function to call DBus method GetPerfOutputFd with the given
40        duration.
41        """
42        pipe_r, pipe_w = os.pipe()
43        perf_command = ['perf', 'record', '-a', '-F', '100']
44
45        session_id = self.dbus_iface.GetPerfOutputFd(
46            duration, perf_command, dbus.types.UnixFd(pipe_w), signature="uash")
47
48        # pipe_w is dup()'d in calling dbus. Close in this process.
49        os.close(pipe_w)
50
51        if session_id == 0:
52            raise error.TestFail('Invalid session ID from GetPerfOutputFd')
53
54        # Don't explicitly os.close(pipe_r) since it will be closed
55        # automatically when the file object returned by os.fdopen() is closed.
56        return session_id, os.fdopen(pipe_r, 'r')
57
58    def call_stop_perf(self, session_id, real_duration):
59        """
60        Utility function to call DBus method StopPerf to collect perf data
61        collected within the given duration.
62        """
63        # Sleep for real_duration seconds and then stop the perf session.
64        time.sleep(real_duration)
65        self.dbus_iface.StopPerf(session_id, signature='t')
66
67    def test_full_duration(self):
68        """
69        Test GetPerfOutpuFd to collect a profile of 2 seconds.
70        """
71
72        session_id, result_file = self.call_get_perf_output_fd(2)
73
74        # This performs synchronous read until perf exits.
75        result = result_file.read()
76
77        self.check_perf_output(result)
78
79    def test_stop_perf(self):
80        """
81        Test StopPerf by calling GetPerfOutputFd to collect a profile of 30
82        seconds. After the perf session is started for 2 seconds, call StopPerf
83        to stop the profiling session. The net result is a profile of 2
84        seconds. Verify StopPerf working by timing the test case: the test case
85        shouldn't run for 30 seconds or longer.
86        """
87        start = time.time()
88
89        # Default duration is 30 sec.
90        session_id, result_file = self.call_get_perf_output_fd(30)
91
92        # Get a profile of 2 seconds by premature stop.
93        self.call_stop_perf(session_id, 2)
94
95        # This performs synchronous read until perf exits.
96        result = result_file.read()
97
98        self.check_perf_output(result)
99
100        end = time.time()
101        if (end - start) >= 30:
102            raise error.TestFail('Unable to stop the perf tool')
103
104    def test_start_after_previous_finished(self):
105        """
106        Test consecutive GetPerfOutputFd calls that there is no undesirable
107        side effect left in the previous profiling session.
108        """
109        self.test_full_duration()
110        self.test_full_duration()
111
112    def test_stop_without_start(self):
113        """
114        Test unmatched StopPerf call by checking the returned DBusException.
115        """
116        dbus_message = None
117        try:
118            self.call_stop_perf(0, 1)
119        except dbus.exceptions.DBusException as dbus_exception:
120            dbus_message = dbus_exception.get_dbus_message()
121
122        if dbus_message is None:
123            raise error.TestFail('DBusException expected')
124        if dbus_message.find('Perf tool not started') == -1:
125            raise error.TestFail('Unexpected DBus message: %s' % dbus_message)
126
127    def test_stop_using_wrong_id(self):
128        """
129        Test calling StopPerf with an invalid session ID by checking the
130        returned DBusException.
131        """
132        start = time.time()
133
134        # Default duration is 30 sec.
135        session_id, result_file = self.call_get_perf_output_fd(30)
136
137        dbus_message = None
138        try:
139            # Use session_id - 1 to trigger the error condition.
140            self.call_stop_perf(session_id - 1, 1)
141        except dbus.exceptions.DBusException as dbus_exception:
142            dbus_message = dbus_exception.get_dbus_message()
143
144        if dbus_message is None:
145            raise error.TestFail('DBusException expected')
146        if dbus_message.find('Invalid profile session id') == -1:
147            raise error.TestFail('Unexpected DBus message: %s' % dbus_message)
148
149        # Get a profile of 1 second by premature stop.
150        self.call_stop_perf(session_id, 1)
151
152        # This performs synchronous read until perf exits.
153        result = result_file.read()
154
155        self.check_perf_output(result)
156
157        end = time.time()
158        if (end - start) >= 30:
159            raise error.TestFail('Unable to stop the perf tool')
160
161    def test_start_2nd_time(self):
162        """
163        Test calling GetPerfOutputFd when an existing profiling session is
164        running: the 2nd call should yield a DBusException without affecting
165        the 1st call.
166        """
167        # Default duration is 30 sec.
168        session_id, result_file = self.call_get_perf_output_fd(30)
169
170        dbus_message = None
171        try:
172            self.call_get_perf_output_fd(60)
173        except dbus.exceptions.DBusException as dbus_exception:
174            dbus_message = dbus_exception.get_dbus_message()
175
176        if dbus_message is None:
177            raise error.TestFail('DBusException expected')
178        if dbus_message.find('Existing perf tool running') == -1:
179            raise error.TestFail('Unexpected DBus message: %s' % dbus_message)
180
181        # Get a profile of 1 second by premature stop.
182        self.call_stop_perf(session_id, 1)
183
184        # This performs synchronous read until perf exits.
185        result = result_file.read()
186
187        self.check_perf_output(result)
188
189    def run_once(self, *args, **kwargs):
190        """
191        Primary autotest function.
192        """
193        # Setup.
194        self.dbus_iface = debugd_util.iface()
195
196        # Test normal cases.
197        self.test_full_duration()
198        self.test_start_after_previous_finished()
199        self.test_stop_perf()
200
201        # Test error cases.
202        self.test_stop_without_start()
203        self.test_stop_using_wrong_id()
204        self.test_start_2nd_time()
205