~jocave/checkbox/hybrid-amd-gpu-mods

« back to all changes in this revision

Viewing changes to checkbox-old/scripts/graphics_stress_test

  • Committer: Tarmac
  • Author(s): Brendan Donegan
  • Date: 2013-06-03 11:12:58 UTC
  • mfrom: (2154.2.1 bug1185759)
  • Revision ID: tarmac-20130603111258-1b3m5ydvkf1accts
"[r=zkrynicki][bug=1185759][author=brendan-donegan] automatic merge by tarmac"

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python3
 
2
# -*- coding: utf-8 -*-
 
3
#
 
4
# graphics_stress_test
 
5
#
 
6
# This file is part of Checkbox.
 
7
#
 
8
# Copyright 2012 Canonical Ltd.
 
9
#
 
10
# Authors: Alberto Milone <alberto.milone@canonical.com>
 
11
#
 
12
# Checkbox is free software: you can redistribute it and/or modify
 
13
# it under the terms of the GNU General Public License as published by
 
14
# the Free Software Foundation, either version 3 of the License, or
 
15
# (at your option) any later version.
 
16
#
 
17
# Checkbox is distributed in the hope that it will be useful,
 
18
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
19
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
20
# GNU General Public License for more details.
 
21
#
 
22
# You should have received a copy of the GNU General Public License
 
23
# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.
 
24
 
 
25
import errno
 
26
import logging
 
27
import os
 
28
import re
 
29
import sys
 
30
import tempfile
 
31
import time
 
32
 
 
33
from argparse import ArgumentParser
 
34
from subprocess import call, Popen, PIPE
 
35
from checkbox.contrib import xrandr
 
36
 
 
37
 
 
38
class VtWrapper(object):
 
39
    """docstring for VtWrapper"""
 
40
    def __init__(self):
 
41
        self.x_vt = self._get_x_vt()
 
42
 
 
43
    def _get_x_vt(self):
 
44
        '''Get the vt where X lives'''
 
45
        vt = 0
 
46
        proc = Popen(['ps', 'aux'], stdout=PIPE, universal_newlines=True)
 
47
        proc_output = proc.communicate()[0].split('\n')
 
48
        proc_line = re.compile('.*tty(\d+).+/usr/bin/X.*')
 
49
        for line in proc_output:
 
50
            match = proc_line.match(line)
 
51
            if match:
 
52
                vt = match.group(1).strip().lower()
 
53
        return int(vt)
 
54
 
 
55
    def set_vt(self, vt):
 
56
        retcode = call(['chvt', '%d' % vt])
 
57
        return retcode
 
58
 
 
59
class SuspendWrapper(object):
 
60
    def __init__(self):
 
61
        pass
 
62
 
 
63
    def can_we_sleep(self, mode):
 
64
        '''
 
65
        Test to see if S3 state is available to us.  /proc/acpi/* is old
 
66
        and will be deprecated, using /sys/power to maintine usefulness for
 
67
        future kernels.
 
68
 
 
69
        '''
 
70
        states_fh = open('/sys/power/state', 'r')
 
71
        try:
 
72
            states = states_fh.read().split()
 
73
        finally:
 
74
            states_fh.close()
 
75
 
 
76
        if mode in states:
 
77
            return True
 
78
        else:
 
79
            return False
 
80
 
 
81
    def get_current_time(self):
 
82
        cur_time = 0
 
83
        time_fh = open('/sys/class/rtc/rtc0/since_epoch', 'r')
 
84
        try:
 
85
            cur_time = int(time_fh.read())
 
86
        finally:
 
87
            time_fh.close()
 
88
        return cur_time
 
89
 
 
90
    def set_wake_time(self, time):
 
91
        '''
 
92
        Get the current epoch time from /sys/class/rtc/rtc0/since_epoch
 
93
        then add time and write our new wake_alarm time to
 
94
        /sys/class/rtc/rtc0/wakealarm.
 
95
 
 
96
        The math could probably be done better but this method avoids having to
 
97
        worry about whether or not we're using UTC or local time for both the
 
98
        hardware and system clocks.
 
99
 
 
100
        '''
 
101
        cur_time = self.get_current_time()
 
102
        logging.debug('Current epoch time: %s' % cur_time)
 
103
 
 
104
        wakealarm_fh = open('/sys/class/rtc/rtc0/wakealarm', 'w')
 
105
 
 
106
        try:
 
107
            wakealarm_fh.write('0\n')
 
108
            wakealarm_fh.flush()
 
109
 
 
110
            wakealarm_fh.write('+%s\n' % time)
 
111
            wakealarm_fh.flush()
 
112
        finally:
 
113
            wakealarm_fh.close()
 
114
 
 
115
        logging.debug('Wake alarm in %s seconds' % time)
 
116
 
 
117
    def do_suspend(self, mode):
 
118
        '''
 
119
        Suspend the system and hope it wakes up.
 
120
        Previously tried writing new state to /sys/power/state but that
 
121
        seems to put the system into an uncrecoverable S3 state.  So far,
 
122
        pm-suspend seems to be the most reliable way to go.
 
123
 
 
124
        '''
 
125
        if mode == 'mem':
 
126
            status = call('/usr/sbin/pm-suspend')
 
127
        elif mode == 'disk':
 
128
            status = call('/usr/sbin/pm-hibernate')
 
129
        else:
 
130
            logging.debug('Unknown sleep state passed')
 
131
            status == 1
 
132
 
 
133
        return status
 
134
 
 
135
class RotationWrapper(object):
 
136
 
 
137
    def __init__(self):
 
138
        self._rotations = {'normal': xrandr.RR_ROTATE_0,
 
139
                           'right': xrandr.RR_ROTATE_90,
 
140
                           'inverted': xrandr.RR_ROTATE_180,
 
141
                           'left': xrandr.RR_ROTATE_270}
 
142
 
 
143
    def _rotate_screen(self, rotation):
 
144
        # Refresh the screen. Required by NVIDIA
 
145
        screen = xrandr.get_current_screen()
 
146
        screen.set_rotation(rotation)
 
147
        return screen.apply_config()
 
148
 
 
149
    def do_rotation_cycle(self):
 
150
        '''Cycle through all possible rotations'''
 
151
        rots_statuses = {}
 
152
 
 
153
        for rot in self._rotations:
 
154
            try:
 
155
                status = self._rotate_screen(self._rotations[rot])
 
156
            except(xrandr.RRError, xrandr.UnsupportedRRError) as err:
 
157
                status = 1
 
158
                error = err
 
159
            else:
 
160
                error = 'N/A'
 
161
            # Collect the status and the error message
 
162
            rots_statuses[rot] = (status, error)
 
163
            time.sleep(4)
 
164
 
 
165
        # Try to set the screen back to normal
 
166
        try:
 
167
            self._rotate_screen(xrandr.RR_ROTATE_0)
 
168
        except(xrandr.RRError, xrandr.UnsupportedRRError) as error:
 
169
            print(error)
 
170
 
 
171
        result = 0
 
172
        for elem in rots_statuses:
 
173
            status = rots_statuses.get(elem)[0]
 
174
            error = rots_statuses.get(elem)[1]
 
175
            if status != 0:
 
176
                logging.error('Error: rotation "%s" failed with status %d: %s.'
 
177
                              % (elem, status, error))
 
178
                result = 1
 
179
        return result
 
180
 
 
181
class RenderCheckWrapper(object):
 
182
    """A simple class to run the rendercheck suites"""
 
183
 
 
184
    def __init__(self, temp_dir=None):
 
185
        self._temp_dir = temp_dir
 
186
 
 
187
    def _print_test_info(self, suites='all', iteration=1, show_errors=False):
 
188
        '''Print the output of the test suite'''
 
189
 
 
190
        main_command = 'rendercheck'
 
191
        passed = 0
 
192
        total = 0
 
193
 
 
194
        if self._temp_dir:
 
195
            # Use the specified path
 
196
            temp_file = tempfile.NamedTemporaryFile(dir=self._temp_dir,
 
197
                                                    delete=False)
 
198
        else:
 
199
            # Use /tmp
 
200
            temp_file = tempfile.NamedTemporaryFile(delete=False)
 
201
 
 
202
        if suites == all:
 
203
            full_command = [main_command, '-f', 'a8r8g8b8']
 
204
        else:
 
205
            full_command = [main_command, '-t', suites, '-f', 'a8r8g8b8']
 
206
 
 
207
        try:
 
208
            # Let's dump the output into file as it can be very large
 
209
            # and we don't want to store it in memory
 
210
            process = Popen(full_command, stdout=temp_file,
 
211
                            universal_newlines=True)
 
212
        except OSError as exc:
 
213
            if exc.errno == errno.ENOENT:
 
214
                logging.error('Error: please make sure that rendercheck '
 
215
                              'is installed.')
 
216
                exit(1)
 
217
            else:
 
218
                raise
 
219
 
 
220
        exit_code = process.wait()
 
221
 
 
222
        temp_file.close()
 
223
 
 
224
        # Read values from the file
 
225
        errors = re.compile('.*test error.*')
 
226
        results = re.compile('(.+) tests passed of (.+) total.*')
 
227
 
 
228
        first_error = True
 
229
        with open(temp_file.name) as temp_handle:
 
230
            for line in temp_handle:
 
231
                match_output = results.match(line)
 
232
                match_errors = errors.match(line)
 
233
                if match_output:
 
234
                    passed = int(match_output.group(1).strip())
 
235
                    total = int(match_output.group(2).strip())
 
236
                    logging.info('Results:')
 
237
                    logging.info('    %d tests passed out of %d.'
 
238
                                  % (passed, total))
 
239
                if show_errors and match_errors:
 
240
                    error = match_errors.group(0).strip()
 
241
                    if first_error:
 
242
                        logging.debug('Rendercheck %s suite errors '
 
243
                                      'from iteration %d:'
 
244
                                       % (suites, iteration))
 
245
                        first_error = False
 
246
                    logging.debug('    %s' % error)
 
247
 
 
248
        # Remove the file
 
249
        os.unlink(temp_file.name)
 
250
 
 
251
        return (exit_code, passed, total)
 
252
 
 
253
    def run_test(self, suites=[], iterations=1, show_errors=False):
 
254
        exit_status = 0
 
255
        for suite in suites:
 
256
            for it in range(iterations):
 
257
                logging.info('Iteration %d of Rendercheck %s suite...'
 
258
                              % (it + 1, suite))
 
259
                (status, passed, total) = \
 
260
                self._print_test_info(suites=suite,
 
261
                                      iteration=it + 1,
 
262
                                      show_errors=show_errors)
 
263
                if status != 0:
 
264
                    # Make sure to catch a non-zero exit status
 
265
                    logging.info('Iteration %d of Rendercheck %s suite '
 
266
                                  'exited with status %d.'
 
267
                                  % (it + 1, suite, status))
 
268
                    exit_status = status
 
269
                it += 1
 
270
 
 
271
                # exit with 1 if passed < total
 
272
                if passed < total:
 
273
                    if exit_status == 0:
 
274
                        exit_status = 1
 
275
        return exit_status
 
276
 
 
277
    def get_suites_list(self):
 
278
        '''Return a list of the available test suites'''
 
279
        try:
 
280
            process = Popen(['rendercheck', '--help'], stdout=PIPE,
 
281
                            stderr=PIPE, universal_newlines=True)
 
282
        except OSError as exc:
 
283
            if exc.errno == errno.ENOENT:
 
284
                logging.error('Error: please make sure that rendercheck '
 
285
                              'is installed.')
 
286
                exit(1)
 
287
            else:
 
288
                raise
 
289
 
 
290
        proc = process.communicate()[1].split('\n')
 
291
        found = False
 
292
        tests_pattern = re.compile('.*Available tests: *(.+).*')
 
293
        temp_line = ''
 
294
        tests = []
 
295
        for line in proc:
 
296
            if found:
 
297
                temp_line += line
 
298
            match = tests_pattern.match(line)
 
299
            if match:
 
300
                first_line = match.group(1).strip().lower()
 
301
                found = True
 
302
                temp_line += first_line
 
303
        for elem in temp_line.split(','):
 
304
            test = elem.strip()
 
305
            if elem:
 
306
                tests.append(test)
 
307
        return tests
 
308
 
 
309
 
 
310
def main():
 
311
    # Make sure that we have root privileges
 
312
    if os.geteuid() != 0:
 
313
        print('Error: please run this program as root',
 
314
              file=sys.stderr)
 
315
        exit(1)
 
316
 
 
317
    usage = 'Usage: %prog [OPTIONS]'
 
318
    parser = ArgumentParser(usage)
 
319
    parser.add_argument('-i', '--iterations',
 
320
                        type=int,
 
321
                        default=10,
 
322
                        help='The number of times to run the test. \
 
323
                              Default is 10')
 
324
    parser.add_argument('-d', '--debug',
 
325
                        action='store_true',
 
326
                        help='Choose this to add verbose output \
 
327
                              for debug purposes')
 
328
    parser.add_argument('-b', '--blacklist',
 
329
                        nargs='+',
 
330
                        help='Name(s) of rendercheck test(s) to blacklist.')
 
331
    parser.add_argument('-o', '--output',
 
332
                        default='',
 
333
                        help='The path to the log which will be dumped. \
 
334
                              Default is stdout')
 
335
    parser.add_argument('-tp', '--temp',
 
336
                        default='',
 
337
                        help='The path where to store temporary files. \
 
338
                              Default is /tmp')
 
339
    args = parser.parse_args()
 
340
 
 
341
    # Set up logging to console
 
342
    format = '%(message)s'
 
343
 
 
344
    console_handler = logging.StreamHandler()
 
345
    console_handler.setFormatter(logging.Formatter(format))
 
346
 
 
347
    # Set up the overall logger
 
348
    logger = logging.getLogger()
 
349
    # This is necessary to ensure debug messages are passed through the logger
 
350
    # to the handler
 
351
    logger.setLevel(logging.DEBUG)
 
352
 
 
353
    # This is what happens when -d and/or -o are passed:
 
354
    # -o ->     stdout (info)                - log (info)
 
355
    # -d ->     only stdout (info and debug) - no log
 
356
    # -d -o ->  stdout (info)                - log (info and debug)
 
357
 
 
358
    # Write to a log
 
359
    if args.output:
 
360
        # Write INFO to stdout
 
361
        console_handler.setLevel(logging.INFO)
 
362
        logger.addHandler(console_handler)
 
363
        # Specify a log file
 
364
        logfile = args.output
 
365
        logfile_handler = logging.FileHandler(logfile)
 
366
        if args.debug:
 
367
            # Write INFO and DEBUG to a log
 
368
            logfile_handler.setLevel(logging.DEBUG)
 
369
        else:
 
370
            # Write INFO to a log
 
371
            logfile_handler.setLevel(logging.INFO)
 
372
 
 
373
        logfile_handler.setFormatter(logging.Formatter(format))
 
374
        logger.addHandler(logfile_handler)
 
375
        log_path = os.path.abspath(logfile)
 
376
 
 
377
    # Write only to stdout
 
378
    else:
 
379
        if args.debug:
 
380
            # Write INFO and DEBUG to stdout
 
381
            console_handler.setLevel(logging.DEBUG)
 
382
            logger.addHandler(console_handler)
 
383
        else:
 
384
            # Write INFO to stdout
 
385
            console_handler.setLevel(logging.INFO)
 
386
            logger.addHandler(console_handler)
 
387
 
 
388
    status = 0
 
389
 
 
390
    rendercheck = RenderCheckWrapper(args.temp)
 
391
    tests = rendercheck.get_suites_list()
 
392
    for test in args.blacklist:
 
393
        if test in tests:
 
394
            tests.remove(test)
 
395
 
 
396
    # Switch between the tty where X lives and tty10
 
397
    vt_wrap = VtWrapper()
 
398
    target_vt = 10
 
399
    if vt_wrap.x_vt != target_vt:
 
400
        logging.info('== Vt switch test ==')
 
401
        for it in range(args.iterations):
 
402
            logging.info('Iteration %d...', it)
 
403
            retcode = vt_wrap.set_vt(target_vt)
 
404
            if retcode != 0:
 
405
                logging.error('Error: switching to tty%d failed with code %d '
 
406
                      'on iteration %d' % (target_vt, retcode, it))
 
407
                status = 1
 
408
            else:
 
409
                logging.info('Switching to tty%d: passed' % (target_vt))
 
410
            time.sleep(2)
 
411
            retcode = vt_wrap.set_vt(vt_wrap.x_vt)
 
412
            if retcode != 0:
 
413
                logging.error('Error: switching to tty%d failed with code %d '
 
414
                      'on iteration %d' % (vt_wrap.x_vt, retcode, it))
 
415
            else:
 
416
                logging.info('Switching to tty%d: passed' % (vt_wrap.x_vt))
 
417
                status = 1
 
418
    else:
 
419
        logging.error('Error: please run X on a tty other than 10')
 
420
 
 
421
    # Call sleep x times
 
422
    logging.info('== Sleep test ==')
 
423
    sleep_test = SuspendWrapper()
 
424
    sleep_mode = 'mem'
 
425
    # See if we can sleep
 
426
    if sleep_test.can_we_sleep(sleep_mode):
 
427
        for it in range(args.iterations):
 
428
            # Run the test
 
429
            logging.info('Iteration %d...', it + 1)
 
430
            # Set wake time
 
431
            sleep_test.set_wake_time(20)
 
432
            # Suspend to RAM
 
433
            if sleep_test.do_suspend(sleep_mode) == 0:
 
434
                logging.info('Passed')
 
435
            else:
 
436
                logging.error('Failed')
 
437
                status = 1
 
438
    else:
 
439
        # Skip the test
 
440
        logging.info('Skipped (the system does not seem to support S3')
 
441
 
 
442
    # Rotate the screen x times
 
443
    # The app already rotates the screen 5 times
 
444
    logging.info('== Rotation test ==')
 
445
    rotation_test = RotationWrapper()
 
446
 
 
447
    for it in range(args.iterations):
 
448
        logging.info('Iteration %d...', it + 1)
 
449
        if rotation_test.do_rotation_cycle() == 0:
 
450
            logging.info('Passed')
 
451
        else:
 
452
            logging.error('Failed')
 
453
            status = 1
 
454
 
 
455
    # Call rendercheck x times
 
456
    logging.info('== Rendercheck test ==')
 
457
    if rendercheck.run_test(tests, args.iterations,
 
458
                            args.debug) == 0:
 
459
        logging.info('Passed')
 
460
    else:
 
461
        logging.error('Failed')
 
462
        status = 1
 
463
 
 
464
    return status
 
465
 
 
466
if __name__ == '__main__':
 
467
    exit(main())