You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
6.1 KiB
197 lines
6.1 KiB
#!/usr/bin/env python
|
|
# Copyright 2017 The PDFium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
"""Measures performance for rendering a single test case with pdfium.
|
|
|
|
The output is a number that is a metric which depends on the profiler specified.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
# pylint: disable=relative-import
|
|
from common import PrintErr
|
|
|
|
CALLGRIND_PROFILER = 'callgrind'
|
|
PERFSTAT_PROFILER = 'perfstat'
|
|
NONE_PROFILER = 'none'
|
|
|
|
PDFIUM_TEST = 'pdfium_test'
|
|
|
|
|
|
class PerformanceRun(object):
|
|
"""A single measurement of a test case."""
|
|
|
|
def __init__(self, args):
|
|
self.args = args
|
|
self.pdfium_test_path = os.path.join(self.args.build_dir, PDFIUM_TEST)
|
|
|
|
def _CheckTools(self):
|
|
"""Returns whether the tool file paths are sane."""
|
|
if not os.path.exists(self.pdfium_test_path):
|
|
PrintErr(
|
|
"FAILURE: Can't find test executable '%s'" % self.pdfium_test_path)
|
|
PrintErr('Use --build-dir to specify its location.')
|
|
return False
|
|
if not os.access(self.pdfium_test_path, os.X_OK):
|
|
PrintErr("FAILURE: Test executable '%s' lacks execution permissions" %
|
|
self.pdfium_test_path)
|
|
return False
|
|
return True
|
|
|
|
def Run(self):
|
|
"""Runs test harness and measures performance with the given profiler.
|
|
|
|
Returns:
|
|
Exit code for the script.
|
|
"""
|
|
if not self._CheckTools():
|
|
return 1
|
|
|
|
if self.args.profiler == CALLGRIND_PROFILER:
|
|
time = self._RunCallgrind()
|
|
elif self.args.profiler == PERFSTAT_PROFILER:
|
|
time = self._RunPerfStat()
|
|
elif self.args.profiler == NONE_PROFILER:
|
|
time = self._RunWithoutProfiler()
|
|
else:
|
|
PrintErr('profiler=%s not supported, aborting' % self.args.profiler)
|
|
return 1
|
|
|
|
if time is None:
|
|
return 1
|
|
|
|
print time
|
|
return 0
|
|
|
|
def _RunCallgrind(self):
|
|
"""Runs test harness and measures performance with callgrind.
|
|
|
|
Returns:
|
|
int with the result of the measurement, in instructions or time.
|
|
"""
|
|
# Whether to turn instrument the whole run or to use the callgrind macro
|
|
# delimiters in pdfium_test.
|
|
instrument_at_start = 'no' if self.args.interesting_section else 'yes'
|
|
output_path = self.args.output_path or '/dev/null'
|
|
|
|
valgrind_cmd = ([
|
|
'valgrind', '--tool=callgrind',
|
|
'--instr-atstart=%s' % instrument_at_start,
|
|
'--callgrind-out-file=%s' % output_path
|
|
] + self._BuildTestHarnessCommand())
|
|
output = subprocess.check_output(valgrind_cmd, stderr=subprocess.STDOUT)
|
|
|
|
# Match the line with the instruction count, eg.
|
|
# '==98765== Collected : 12345'
|
|
return self._ExtractIrCount(r'\bCollected\b *: *\b(\d+)', output)
|
|
|
|
def _RunPerfStat(self):
|
|
"""Runs test harness and measures performance with perf stat.
|
|
|
|
Returns:
|
|
int with the result of the measurement, in instructions or time.
|
|
"""
|
|
# --no-big-num: do not add thousands separators
|
|
# -einstructions: print only instruction count
|
|
cmd_to_run = (['perf', 'stat', '--no-big-num', '-einstructions'] +
|
|
self._BuildTestHarnessCommand())
|
|
output = subprocess.check_output(cmd_to_run, stderr=subprocess.STDOUT)
|
|
|
|
# Match the line with the instruction count, eg.
|
|
# ' 12345 instructions'
|
|
return self._ExtractIrCount(r'\b(\d+)\b.*\binstructions\b', output)
|
|
|
|
def _RunWithoutProfiler(self):
|
|
"""Runs test harness and measures performance without a profiler.
|
|
|
|
Returns:
|
|
int with the result of the measurement, in instructions or time. In this
|
|
case, always return 1 since no profiler is being used.
|
|
"""
|
|
cmd_to_run = self._BuildTestHarnessCommand()
|
|
subprocess.check_output(cmd_to_run, stderr=subprocess.STDOUT)
|
|
|
|
# Return 1 for every run.
|
|
return 1
|
|
|
|
def _BuildTestHarnessCommand(self):
|
|
"""Builds command to run the test harness."""
|
|
cmd = [self.pdfium_test_path, '--send-events']
|
|
|
|
if self.args.interesting_section:
|
|
cmd.append('--callgrind-delim')
|
|
if self.args.png:
|
|
cmd.append('--png')
|
|
if self.args.pages:
|
|
cmd.append('--pages=%s' % self.args.pages)
|
|
|
|
cmd.append(self.args.pdf_path)
|
|
return cmd
|
|
|
|
def _ExtractIrCount(self, regex, output):
|
|
"""Extracts a number from the output with a regex."""
|
|
matched = re.search(regex, output)
|
|
|
|
if not matched:
|
|
return None
|
|
|
|
# Group 1 is the instruction number, eg. 12345
|
|
return int(matched.group(1))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
'pdf_path', help='test case to measure load and rendering time')
|
|
parser.add_argument(
|
|
'--build-dir',
|
|
default=os.path.join('out', 'Release'),
|
|
help='relative path to the build directory with '
|
|
'%s' % PDFIUM_TEST)
|
|
parser.add_argument(
|
|
'--profiler',
|
|
default=CALLGRIND_PROFILER,
|
|
help='which profiler to use. Supports callgrind, '
|
|
'perfstat, and none.')
|
|
parser.add_argument(
|
|
'--interesting-section',
|
|
action='store_true',
|
|
help='whether to measure just the interesting section or '
|
|
'the whole test harness. The interesting section is '
|
|
'pdfium reading a pdf from memory and rendering '
|
|
'it, which omits loading the time to load the file, '
|
|
'initialize the library, terminate it, etc. '
|
|
'Limiting to only the interesting section does not '
|
|
'work on Release since the delimiters are optimized '
|
|
'out. Callgrind only.')
|
|
parser.add_argument(
|
|
'--png',
|
|
action='store_true',
|
|
help='outputs a png image on the same location as the '
|
|
'pdf file')
|
|
parser.add_argument(
|
|
'--pages',
|
|
help='selects some pages to be rendered. Page numbers '
|
|
'are 0-based. "--pages A" will render only page A. '
|
|
'"--pages A-B" will render pages A to B '
|
|
'(inclusive).')
|
|
parser.add_argument(
|
|
'--output-path', help='where to write the profile data output file')
|
|
args = parser.parse_args()
|
|
|
|
if args.interesting_section and args.profiler != CALLGRIND_PROFILER:
|
|
PrintErr('--interesting-section requires profiler to be callgrind.')
|
|
return 1
|
|
|
|
run = PerformanceRun(args)
|
|
return run.Run()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|