1*3ac0a46fSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*3ac0a46fSAndroid Build Coastguard Worker# Copyright 2017 The PDFium Authors 3*3ac0a46fSAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*3ac0a46fSAndroid Build Coastguard Worker# found in the LICENSE file. 5*3ac0a46fSAndroid Build Coastguard Worker"""Measures performance for rendering a single test case with pdfium. 6*3ac0a46fSAndroid Build Coastguard Worker 7*3ac0a46fSAndroid Build Coastguard WorkerThe output is a number that is a metric which depends on the profiler specified. 8*3ac0a46fSAndroid Build Coastguard Worker""" 9*3ac0a46fSAndroid Build Coastguard Worker 10*3ac0a46fSAndroid Build Coastguard Workerimport argparse 11*3ac0a46fSAndroid Build Coastguard Workerimport os 12*3ac0a46fSAndroid Build Coastguard Workerimport re 13*3ac0a46fSAndroid Build Coastguard Workerimport subprocess 14*3ac0a46fSAndroid Build Coastguard Workerimport sys 15*3ac0a46fSAndroid Build Coastguard Worker 16*3ac0a46fSAndroid Build Coastguard Workerfrom common import PrintErr 17*3ac0a46fSAndroid Build Coastguard Worker 18*3ac0a46fSAndroid Build Coastguard WorkerCALLGRIND_PROFILER = 'callgrind' 19*3ac0a46fSAndroid Build Coastguard WorkerPERFSTAT_PROFILER = 'perfstat' 20*3ac0a46fSAndroid Build Coastguard WorkerNONE_PROFILER = 'none' 21*3ac0a46fSAndroid Build Coastguard Worker 22*3ac0a46fSAndroid Build Coastguard WorkerPDFIUM_TEST = 'pdfium_test' 23*3ac0a46fSAndroid Build Coastguard Worker 24*3ac0a46fSAndroid Build Coastguard Worker 25*3ac0a46fSAndroid Build Coastguard Workerclass PerformanceRun: 26*3ac0a46fSAndroid Build Coastguard Worker """A single measurement of a test case.""" 27*3ac0a46fSAndroid Build Coastguard Worker 28*3ac0a46fSAndroid Build Coastguard Worker def __init__(self, args): 29*3ac0a46fSAndroid Build Coastguard Worker self.args = args 30*3ac0a46fSAndroid Build Coastguard Worker self.pdfium_test_path = os.path.join(self.args.build_dir, PDFIUM_TEST) 31*3ac0a46fSAndroid Build Coastguard Worker 32*3ac0a46fSAndroid Build Coastguard Worker def _CheckTools(self): 33*3ac0a46fSAndroid Build Coastguard Worker """Returns whether the tool file paths are sane.""" 34*3ac0a46fSAndroid Build Coastguard Worker if not os.path.exists(self.pdfium_test_path): 35*3ac0a46fSAndroid Build Coastguard Worker PrintErr( 36*3ac0a46fSAndroid Build Coastguard Worker "FAILURE: Can't find test executable '%s'" % self.pdfium_test_path) 37*3ac0a46fSAndroid Build Coastguard Worker PrintErr('Use --build-dir to specify its location.') 38*3ac0a46fSAndroid Build Coastguard Worker return False 39*3ac0a46fSAndroid Build Coastguard Worker if not os.access(self.pdfium_test_path, os.X_OK): 40*3ac0a46fSAndroid Build Coastguard Worker PrintErr("FAILURE: Test executable '%s' lacks execution permissions" % 41*3ac0a46fSAndroid Build Coastguard Worker self.pdfium_test_path) 42*3ac0a46fSAndroid Build Coastguard Worker return False 43*3ac0a46fSAndroid Build Coastguard Worker return True 44*3ac0a46fSAndroid Build Coastguard Worker 45*3ac0a46fSAndroid Build Coastguard Worker def Run(self): 46*3ac0a46fSAndroid Build Coastguard Worker """Runs test harness and measures performance with the given profiler. 47*3ac0a46fSAndroid Build Coastguard Worker 48*3ac0a46fSAndroid Build Coastguard Worker Returns: 49*3ac0a46fSAndroid Build Coastguard Worker Exit code for the script. 50*3ac0a46fSAndroid Build Coastguard Worker """ 51*3ac0a46fSAndroid Build Coastguard Worker if not self._CheckTools(): 52*3ac0a46fSAndroid Build Coastguard Worker return 1 53*3ac0a46fSAndroid Build Coastguard Worker 54*3ac0a46fSAndroid Build Coastguard Worker if self.args.profiler == CALLGRIND_PROFILER: 55*3ac0a46fSAndroid Build Coastguard Worker time = self._RunCallgrind() 56*3ac0a46fSAndroid Build Coastguard Worker elif self.args.profiler == PERFSTAT_PROFILER: 57*3ac0a46fSAndroid Build Coastguard Worker time = self._RunPerfStat() 58*3ac0a46fSAndroid Build Coastguard Worker elif self.args.profiler == NONE_PROFILER: 59*3ac0a46fSAndroid Build Coastguard Worker time = self._RunWithoutProfiler() 60*3ac0a46fSAndroid Build Coastguard Worker else: 61*3ac0a46fSAndroid Build Coastguard Worker PrintErr('profiler=%s not supported, aborting' % self.args.profiler) 62*3ac0a46fSAndroid Build Coastguard Worker return 1 63*3ac0a46fSAndroid Build Coastguard Worker 64*3ac0a46fSAndroid Build Coastguard Worker if time is None: 65*3ac0a46fSAndroid Build Coastguard Worker return 1 66*3ac0a46fSAndroid Build Coastguard Worker 67*3ac0a46fSAndroid Build Coastguard Worker print(time) 68*3ac0a46fSAndroid Build Coastguard Worker return 0 69*3ac0a46fSAndroid Build Coastguard Worker 70*3ac0a46fSAndroid Build Coastguard Worker def _RunCallgrind(self): 71*3ac0a46fSAndroid Build Coastguard Worker """Runs test harness and measures performance with callgrind. 72*3ac0a46fSAndroid Build Coastguard Worker 73*3ac0a46fSAndroid Build Coastguard Worker Returns: 74*3ac0a46fSAndroid Build Coastguard Worker int with the result of the measurement, in instructions or time. 75*3ac0a46fSAndroid Build Coastguard Worker """ 76*3ac0a46fSAndroid Build Coastguard Worker # Whether to turn instrument the whole run or to use the callgrind macro 77*3ac0a46fSAndroid Build Coastguard Worker # delimiters in pdfium_test. 78*3ac0a46fSAndroid Build Coastguard Worker instrument_at_start = 'no' if self.args.interesting_section else 'yes' 79*3ac0a46fSAndroid Build Coastguard Worker output_path = self.args.output_path or '/dev/null' 80*3ac0a46fSAndroid Build Coastguard Worker 81*3ac0a46fSAndroid Build Coastguard Worker valgrind_cmd = ([ 82*3ac0a46fSAndroid Build Coastguard Worker 'valgrind', '--tool=callgrind', 83*3ac0a46fSAndroid Build Coastguard Worker '--instr-atstart=%s' % instrument_at_start, 84*3ac0a46fSAndroid Build Coastguard Worker '--callgrind-out-file=%s' % output_path 85*3ac0a46fSAndroid Build Coastguard Worker ] + self._BuildTestHarnessCommand()) 86*3ac0a46fSAndroid Build Coastguard Worker output = subprocess.check_output( 87*3ac0a46fSAndroid Build Coastguard Worker valgrind_cmd, stderr=subprocess.STDOUT).decode('utf-8') 88*3ac0a46fSAndroid Build Coastguard Worker 89*3ac0a46fSAndroid Build Coastguard Worker # Match the line with the instruction count, eg. 90*3ac0a46fSAndroid Build Coastguard Worker # '==98765== Collected : 12345' 91*3ac0a46fSAndroid Build Coastguard Worker return self._ExtractIrCount(r'\bCollected\b *: *\b(\d+)', output) 92*3ac0a46fSAndroid Build Coastguard Worker 93*3ac0a46fSAndroid Build Coastguard Worker def _RunPerfStat(self): 94*3ac0a46fSAndroid Build Coastguard Worker """Runs test harness and measures performance with perf stat. 95*3ac0a46fSAndroid Build Coastguard Worker 96*3ac0a46fSAndroid Build Coastguard Worker Returns: 97*3ac0a46fSAndroid Build Coastguard Worker int with the result of the measurement, in instructions or time. 98*3ac0a46fSAndroid Build Coastguard Worker """ 99*3ac0a46fSAndroid Build Coastguard Worker # --no-big-num: do not add thousands separators 100*3ac0a46fSAndroid Build Coastguard Worker # -einstructions: print only instruction count 101*3ac0a46fSAndroid Build Coastguard Worker cmd_to_run = (['perf', 'stat', '--no-big-num', '-einstructions'] + 102*3ac0a46fSAndroid Build Coastguard Worker self._BuildTestHarnessCommand()) 103*3ac0a46fSAndroid Build Coastguard Worker output = subprocess.check_output( 104*3ac0a46fSAndroid Build Coastguard Worker cmd_to_run, stderr=subprocess.STDOUT).decode('utf-8') 105*3ac0a46fSAndroid Build Coastguard Worker 106*3ac0a46fSAndroid Build Coastguard Worker # Match the line with the instruction count, eg. 107*3ac0a46fSAndroid Build Coastguard Worker # ' 12345 instructions' 108*3ac0a46fSAndroid Build Coastguard Worker return self._ExtractIrCount(r'\b(\d+)\b.*\binstructions\b', output) 109*3ac0a46fSAndroid Build Coastguard Worker 110*3ac0a46fSAndroid Build Coastguard Worker def _RunWithoutProfiler(self): 111*3ac0a46fSAndroid Build Coastguard Worker """Runs test harness and measures performance without a profiler. 112*3ac0a46fSAndroid Build Coastguard Worker 113*3ac0a46fSAndroid Build Coastguard Worker Returns: 114*3ac0a46fSAndroid Build Coastguard Worker int with the result of the measurement, in instructions or time. In this 115*3ac0a46fSAndroid Build Coastguard Worker case, always return 1 since no profiler is being used. 116*3ac0a46fSAndroid Build Coastguard Worker """ 117*3ac0a46fSAndroid Build Coastguard Worker cmd_to_run = self._BuildTestHarnessCommand() 118*3ac0a46fSAndroid Build Coastguard Worker subprocess.check_output(cmd_to_run, stderr=subprocess.STDOUT) 119*3ac0a46fSAndroid Build Coastguard Worker 120*3ac0a46fSAndroid Build Coastguard Worker # Return 1 for every run. 121*3ac0a46fSAndroid Build Coastguard Worker return 1 122*3ac0a46fSAndroid Build Coastguard Worker 123*3ac0a46fSAndroid Build Coastguard Worker def _BuildTestHarnessCommand(self): 124*3ac0a46fSAndroid Build Coastguard Worker """Builds command to run the test harness.""" 125*3ac0a46fSAndroid Build Coastguard Worker cmd = [self.pdfium_test_path, '--send-events'] 126*3ac0a46fSAndroid Build Coastguard Worker 127*3ac0a46fSAndroid Build Coastguard Worker if self.args.interesting_section: 128*3ac0a46fSAndroid Build Coastguard Worker cmd.append('--callgrind-delim') 129*3ac0a46fSAndroid Build Coastguard Worker if self.args.png: 130*3ac0a46fSAndroid Build Coastguard Worker cmd.append('--png') 131*3ac0a46fSAndroid Build Coastguard Worker if self.args.pages: 132*3ac0a46fSAndroid Build Coastguard Worker cmd.append('--pages=%s' % self.args.pages) 133*3ac0a46fSAndroid Build Coastguard Worker 134*3ac0a46fSAndroid Build Coastguard Worker cmd.append(self.args.pdf_path) 135*3ac0a46fSAndroid Build Coastguard Worker return cmd 136*3ac0a46fSAndroid Build Coastguard Worker 137*3ac0a46fSAndroid Build Coastguard Worker def _ExtractIrCount(self, regex, output): 138*3ac0a46fSAndroid Build Coastguard Worker """Extracts a number from the output with a regex.""" 139*3ac0a46fSAndroid Build Coastguard Worker matched = re.search(regex, output) 140*3ac0a46fSAndroid Build Coastguard Worker 141*3ac0a46fSAndroid Build Coastguard Worker if not matched: 142*3ac0a46fSAndroid Build Coastguard Worker return None 143*3ac0a46fSAndroid Build Coastguard Worker 144*3ac0a46fSAndroid Build Coastguard Worker # Group 1 is the instruction number, eg. 12345 145*3ac0a46fSAndroid Build Coastguard Worker return int(matched.group(1)) 146*3ac0a46fSAndroid Build Coastguard Worker 147*3ac0a46fSAndroid Build Coastguard Worker 148*3ac0a46fSAndroid Build Coastguard Workerdef main(): 149*3ac0a46fSAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 150*3ac0a46fSAndroid Build Coastguard Worker parser.add_argument( 151*3ac0a46fSAndroid Build Coastguard Worker 'pdf_path', help='test case to measure load and rendering time') 152*3ac0a46fSAndroid Build Coastguard Worker parser.add_argument( 153*3ac0a46fSAndroid Build Coastguard Worker '--build-dir', 154*3ac0a46fSAndroid Build Coastguard Worker default=os.path.join('out', 'Release'), 155*3ac0a46fSAndroid Build Coastguard Worker help='relative path to the build directory with ' 156*3ac0a46fSAndroid Build Coastguard Worker '%s' % PDFIUM_TEST) 157*3ac0a46fSAndroid Build Coastguard Worker parser.add_argument( 158*3ac0a46fSAndroid Build Coastguard Worker '--profiler', 159*3ac0a46fSAndroid Build Coastguard Worker default=CALLGRIND_PROFILER, 160*3ac0a46fSAndroid Build Coastguard Worker help='which profiler to use. Supports callgrind, ' 161*3ac0a46fSAndroid Build Coastguard Worker 'perfstat, and none.') 162*3ac0a46fSAndroid Build Coastguard Worker parser.add_argument( 163*3ac0a46fSAndroid Build Coastguard Worker '--interesting-section', 164*3ac0a46fSAndroid Build Coastguard Worker action='store_true', 165*3ac0a46fSAndroid Build Coastguard Worker help='whether to measure just the interesting section or ' 166*3ac0a46fSAndroid Build Coastguard Worker 'the whole test harness. The interesting section is ' 167*3ac0a46fSAndroid Build Coastguard Worker 'pdfium reading a pdf from memory and rendering ' 168*3ac0a46fSAndroid Build Coastguard Worker 'it, which omits loading the time to load the file, ' 169*3ac0a46fSAndroid Build Coastguard Worker 'initialize the library, terminate it, etc. ' 170*3ac0a46fSAndroid Build Coastguard Worker 'Limiting to only the interesting section does not ' 171*3ac0a46fSAndroid Build Coastguard Worker 'work on Release since the delimiters are optimized ' 172*3ac0a46fSAndroid Build Coastguard Worker 'out. Callgrind only.') 173*3ac0a46fSAndroid Build Coastguard Worker parser.add_argument( 174*3ac0a46fSAndroid Build Coastguard Worker '--png', 175*3ac0a46fSAndroid Build Coastguard Worker action='store_true', 176*3ac0a46fSAndroid Build Coastguard Worker help='outputs a png image on the same location as the ' 177*3ac0a46fSAndroid Build Coastguard Worker 'pdf file') 178*3ac0a46fSAndroid Build Coastguard Worker parser.add_argument( 179*3ac0a46fSAndroid Build Coastguard Worker '--pages', 180*3ac0a46fSAndroid Build Coastguard Worker help='selects some pages to be rendered. Page numbers ' 181*3ac0a46fSAndroid Build Coastguard Worker 'are 0-based. "--pages A" will render only page A. ' 182*3ac0a46fSAndroid Build Coastguard Worker '"--pages A-B" will render pages A to B ' 183*3ac0a46fSAndroid Build Coastguard Worker '(inclusive).') 184*3ac0a46fSAndroid Build Coastguard Worker parser.add_argument( 185*3ac0a46fSAndroid Build Coastguard Worker '--output-path', help='where to write the profile data output file') 186*3ac0a46fSAndroid Build Coastguard Worker args = parser.parse_args() 187*3ac0a46fSAndroid Build Coastguard Worker 188*3ac0a46fSAndroid Build Coastguard Worker if args.interesting_section and args.profiler != CALLGRIND_PROFILER: 189*3ac0a46fSAndroid Build Coastguard Worker PrintErr('--interesting-section requires profiler to be callgrind.') 190*3ac0a46fSAndroid Build Coastguard Worker return 1 191*3ac0a46fSAndroid Build Coastguard Worker 192*3ac0a46fSAndroid Build Coastguard Worker run = PerformanceRun(args) 193*3ac0a46fSAndroid Build Coastguard Worker return run.Run() 194*3ac0a46fSAndroid Build Coastguard Worker 195*3ac0a46fSAndroid Build Coastguard Worker 196*3ac0a46fSAndroid Build Coastguard Workerif __name__ == '__main__': 197*3ac0a46fSAndroid Build Coastguard Worker sys.exit(main()) 198