xref: /aosp_15_r20/external/pdfium/testing/tools/safetynet_measure.py (revision 3ac0a46f773bac49fa9476ec2b1cf3f8da5ec3a4)
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