xref: /aosp_15_r20/external/pdfium/testing/tools/safetynet_compare.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"""Compares the performance of two versions of the pdfium code."""
6*3ac0a46fSAndroid Build Coastguard Worker
7*3ac0a46fSAndroid Build Coastguard Workerimport argparse
8*3ac0a46fSAndroid Build Coastguard Workerimport functools
9*3ac0a46fSAndroid Build Coastguard Workerimport glob
10*3ac0a46fSAndroid Build Coastguard Workerimport json
11*3ac0a46fSAndroid Build Coastguard Workerimport multiprocessing
12*3ac0a46fSAndroid Build Coastguard Workerimport os
13*3ac0a46fSAndroid Build Coastguard Workerimport re
14*3ac0a46fSAndroid Build Coastguard Workerimport shutil
15*3ac0a46fSAndroid Build Coastguard Workerimport subprocess
16*3ac0a46fSAndroid Build Coastguard Workerimport sys
17*3ac0a46fSAndroid Build Coastguard Workerimport tempfile
18*3ac0a46fSAndroid Build Coastguard Worker
19*3ac0a46fSAndroid Build Coastguard Workerfrom common import GetBooleanGnArg
20*3ac0a46fSAndroid Build Coastguard Workerfrom common import PrintErr
21*3ac0a46fSAndroid Build Coastguard Workerfrom common import RunCommandPropagateErr
22*3ac0a46fSAndroid Build Coastguard Workerfrom githelper import GitHelper
23*3ac0a46fSAndroid Build Coastguard Workerfrom safetynet_conclusions import ComparisonConclusions
24*3ac0a46fSAndroid Build Coastguard Workerfrom safetynet_conclusions import PrintConclusionsDictHumanReadable
25*3ac0a46fSAndroid Build Coastguard Workerfrom safetynet_conclusions import RATING_IMPROVEMENT
26*3ac0a46fSAndroid Build Coastguard Workerfrom safetynet_conclusions import RATING_REGRESSION
27*3ac0a46fSAndroid Build Coastguard Workerfrom safetynet_image import ImageComparison
28*3ac0a46fSAndroid Build Coastguard Worker
29*3ac0a46fSAndroid Build Coastguard Worker
30*3ac0a46fSAndroid Build Coastguard Workerdef RunSingleTestCaseParallel(this, run_label, build_dir, test_case):
31*3ac0a46fSAndroid Build Coastguard Worker  result = this.RunSingleTestCase(run_label, build_dir, test_case)
32*3ac0a46fSAndroid Build Coastguard Worker  return (test_case, result)
33*3ac0a46fSAndroid Build Coastguard Worker
34*3ac0a46fSAndroid Build Coastguard Worker
35*3ac0a46fSAndroid Build Coastguard Workerclass CompareRun:
36*3ac0a46fSAndroid Build Coastguard Worker  """A comparison between two branches of pdfium."""
37*3ac0a46fSAndroid Build Coastguard Worker
38*3ac0a46fSAndroid Build Coastguard Worker  def __init__(self, args):
39*3ac0a46fSAndroid Build Coastguard Worker    self.git = GitHelper()
40*3ac0a46fSAndroid Build Coastguard Worker    self.args = args
41*3ac0a46fSAndroid Build Coastguard Worker    self._InitPaths()
42*3ac0a46fSAndroid Build Coastguard Worker
43*3ac0a46fSAndroid Build Coastguard Worker  def _InitPaths(self):
44*3ac0a46fSAndroid Build Coastguard Worker    if self.args.this_repo:
45*3ac0a46fSAndroid Build Coastguard Worker      self.safe_script_dir = self.args.build_dir
46*3ac0a46fSAndroid Build Coastguard Worker    else:
47*3ac0a46fSAndroid Build Coastguard Worker      self.safe_script_dir = os.path.join('testing', 'tools')
48*3ac0a46fSAndroid Build Coastguard Worker
49*3ac0a46fSAndroid Build Coastguard Worker    self.safe_measure_script_path = os.path.abspath(
50*3ac0a46fSAndroid Build Coastguard Worker        os.path.join(self.safe_script_dir, 'safetynet_measure.py'))
51*3ac0a46fSAndroid Build Coastguard Worker
52*3ac0a46fSAndroid Build Coastguard Worker    input_file_re = re.compile('^.+[.]pdf$')
53*3ac0a46fSAndroid Build Coastguard Worker    self.test_cases = []
54*3ac0a46fSAndroid Build Coastguard Worker    for input_path in self.args.input_paths:
55*3ac0a46fSAndroid Build Coastguard Worker      if os.path.isfile(input_path):
56*3ac0a46fSAndroid Build Coastguard Worker        self.test_cases.append(input_path)
57*3ac0a46fSAndroid Build Coastguard Worker      elif os.path.isdir(input_path):
58*3ac0a46fSAndroid Build Coastguard Worker        for file_dir, _, filename_list in os.walk(input_path):
59*3ac0a46fSAndroid Build Coastguard Worker          for input_filename in filename_list:
60*3ac0a46fSAndroid Build Coastguard Worker            if input_file_re.match(input_filename):
61*3ac0a46fSAndroid Build Coastguard Worker              file_path = os.path.join(file_dir, input_filename)
62*3ac0a46fSAndroid Build Coastguard Worker              if os.path.isfile(file_path):
63*3ac0a46fSAndroid Build Coastguard Worker                self.test_cases.append(file_path)
64*3ac0a46fSAndroid Build Coastguard Worker
65*3ac0a46fSAndroid Build Coastguard Worker    self.after_build_dir = self.args.build_dir
66*3ac0a46fSAndroid Build Coastguard Worker    if self.args.build_dir_before:
67*3ac0a46fSAndroid Build Coastguard Worker      self.before_build_dir = self.args.build_dir_before
68*3ac0a46fSAndroid Build Coastguard Worker    else:
69*3ac0a46fSAndroid Build Coastguard Worker      self.before_build_dir = self.after_build_dir
70*3ac0a46fSAndroid Build Coastguard Worker
71*3ac0a46fSAndroid Build Coastguard Worker  def Run(self):
72*3ac0a46fSAndroid Build Coastguard Worker    """Runs comparison by checking out branches, building and measuring them.
73*3ac0a46fSAndroid Build Coastguard Worker
74*3ac0a46fSAndroid Build Coastguard Worker    Returns:
75*3ac0a46fSAndroid Build Coastguard Worker      Exit code for the script.
76*3ac0a46fSAndroid Build Coastguard Worker    """
77*3ac0a46fSAndroid Build Coastguard Worker    if self.args.this_repo:
78*3ac0a46fSAndroid Build Coastguard Worker      self._FreezeMeasureScript()
79*3ac0a46fSAndroid Build Coastguard Worker
80*3ac0a46fSAndroid Build Coastguard Worker    if self.args.branch_after:
81*3ac0a46fSAndroid Build Coastguard Worker      if self.args.this_repo:
82*3ac0a46fSAndroid Build Coastguard Worker        before, after = self._ProfileTwoOtherBranchesInThisRepo(
83*3ac0a46fSAndroid Build Coastguard Worker            self.args.branch_before, self.args.branch_after)
84*3ac0a46fSAndroid Build Coastguard Worker      else:
85*3ac0a46fSAndroid Build Coastguard Worker        before, after = self._ProfileTwoOtherBranches(self.args.branch_before,
86*3ac0a46fSAndroid Build Coastguard Worker                                                      self.args.branch_after)
87*3ac0a46fSAndroid Build Coastguard Worker    elif self.args.branch_before:
88*3ac0a46fSAndroid Build Coastguard Worker      if self.args.this_repo:
89*3ac0a46fSAndroid Build Coastguard Worker        before, after = self._ProfileCurrentAndOtherBranchInThisRepo(
90*3ac0a46fSAndroid Build Coastguard Worker            self.args.branch_before)
91*3ac0a46fSAndroid Build Coastguard Worker      else:
92*3ac0a46fSAndroid Build Coastguard Worker        before, after = self._ProfileCurrentAndOtherBranch(
93*3ac0a46fSAndroid Build Coastguard Worker            self.args.branch_before)
94*3ac0a46fSAndroid Build Coastguard Worker    else:
95*3ac0a46fSAndroid Build Coastguard Worker      if self.args.this_repo:
96*3ac0a46fSAndroid Build Coastguard Worker        before, after = self._ProfileLocalChangesAndCurrentBranchInThisRepo()
97*3ac0a46fSAndroid Build Coastguard Worker      else:
98*3ac0a46fSAndroid Build Coastguard Worker        before, after = self._ProfileLocalChangesAndCurrentBranch()
99*3ac0a46fSAndroid Build Coastguard Worker
100*3ac0a46fSAndroid Build Coastguard Worker    conclusions = self._DrawConclusions(before, after)
101*3ac0a46fSAndroid Build Coastguard Worker    conclusions_dict = conclusions.GetOutputDict()
102*3ac0a46fSAndroid Build Coastguard Worker    conclusions_dict.setdefault('metadata', {})['profiler'] = self.args.profiler
103*3ac0a46fSAndroid Build Coastguard Worker
104*3ac0a46fSAndroid Build Coastguard Worker    self._PrintConclusions(conclusions_dict)
105*3ac0a46fSAndroid Build Coastguard Worker
106*3ac0a46fSAndroid Build Coastguard Worker    self._CleanUp(conclusions)
107*3ac0a46fSAndroid Build Coastguard Worker
108*3ac0a46fSAndroid Build Coastguard Worker    if self.args.png_dir:
109*3ac0a46fSAndroid Build Coastguard Worker      image_comparison = ImageComparison(
110*3ac0a46fSAndroid Build Coastguard Worker          self.after_build_dir, self.args.png_dir, ('before', 'after'),
111*3ac0a46fSAndroid Build Coastguard Worker          self.args.num_workers, self.args.png_threshold)
112*3ac0a46fSAndroid Build Coastguard Worker      image_comparison.Run(open_in_browser=not self.args.machine_readable)
113*3ac0a46fSAndroid Build Coastguard Worker
114*3ac0a46fSAndroid Build Coastguard Worker    return 0
115*3ac0a46fSAndroid Build Coastguard Worker
116*3ac0a46fSAndroid Build Coastguard Worker  def _FreezeMeasureScript(self):
117*3ac0a46fSAndroid Build Coastguard Worker    """Freezes a version of the measuring script.
118*3ac0a46fSAndroid Build Coastguard Worker
119*3ac0a46fSAndroid Build Coastguard Worker    This is needed to make sure we are comparing the pdfium library changes and
120*3ac0a46fSAndroid Build Coastguard Worker    not script changes that may happen between the two branches.
121*3ac0a46fSAndroid Build Coastguard Worker    """
122*3ac0a46fSAndroid Build Coastguard Worker    self.__FreezeFile(os.path.join('testing', 'tools', 'safetynet_measure.py'))
123*3ac0a46fSAndroid Build Coastguard Worker    self.__FreezeFile(os.path.join('testing', 'tools', 'common.py'))
124*3ac0a46fSAndroid Build Coastguard Worker
125*3ac0a46fSAndroid Build Coastguard Worker  def __FreezeFile(self, filename):
126*3ac0a46fSAndroid Build Coastguard Worker    RunCommandPropagateErr(['cp', filename, self.safe_script_dir],
127*3ac0a46fSAndroid Build Coastguard Worker                           exit_status_on_error=1)
128*3ac0a46fSAndroid Build Coastguard Worker
129*3ac0a46fSAndroid Build Coastguard Worker  def _ProfileTwoOtherBranchesInThisRepo(self, before_branch, after_branch):
130*3ac0a46fSAndroid Build Coastguard Worker    """Profiles two branches that are not the current branch.
131*3ac0a46fSAndroid Build Coastguard Worker
132*3ac0a46fSAndroid Build Coastguard Worker    This is done in the local repository and changes may not be restored if the
133*3ac0a46fSAndroid Build Coastguard Worker    script fails or is interrupted.
134*3ac0a46fSAndroid Build Coastguard Worker
135*3ac0a46fSAndroid Build Coastguard Worker    after_branch does not need to descend from before_branch, they will be
136*3ac0a46fSAndroid Build Coastguard Worker    measured the same way
137*3ac0a46fSAndroid Build Coastguard Worker
138*3ac0a46fSAndroid Build Coastguard Worker    Args:
139*3ac0a46fSAndroid Build Coastguard Worker      before_branch: One branch to profile.
140*3ac0a46fSAndroid Build Coastguard Worker      after_branch: Other branch to profile.
141*3ac0a46fSAndroid Build Coastguard Worker
142*3ac0a46fSAndroid Build Coastguard Worker    Returns:
143*3ac0a46fSAndroid Build Coastguard Worker      A tuple (before, after), where each of before and after is a dict
144*3ac0a46fSAndroid Build Coastguard Worker      mapping a test case name to the profiling values for that test case
145*3ac0a46fSAndroid Build Coastguard Worker      in the given branch.
146*3ac0a46fSAndroid Build Coastguard Worker    """
147*3ac0a46fSAndroid Build Coastguard Worker    branch_to_restore = self.git.GetCurrentBranchName()
148*3ac0a46fSAndroid Build Coastguard Worker
149*3ac0a46fSAndroid Build Coastguard Worker    self._StashLocalChanges()
150*3ac0a46fSAndroid Build Coastguard Worker
151*3ac0a46fSAndroid Build Coastguard Worker    self._CheckoutBranch(after_branch)
152*3ac0a46fSAndroid Build Coastguard Worker    self._BuildCurrentBranch(self.after_build_dir)
153*3ac0a46fSAndroid Build Coastguard Worker    after = self._MeasureCurrentBranch('after', self.after_build_dir)
154*3ac0a46fSAndroid Build Coastguard Worker
155*3ac0a46fSAndroid Build Coastguard Worker    self._CheckoutBranch(before_branch)
156*3ac0a46fSAndroid Build Coastguard Worker    self._BuildCurrentBranch(self.before_build_dir)
157*3ac0a46fSAndroid Build Coastguard Worker    before = self._MeasureCurrentBranch('before', self.before_build_dir)
158*3ac0a46fSAndroid Build Coastguard Worker
159*3ac0a46fSAndroid Build Coastguard Worker    self._CheckoutBranch(branch_to_restore)
160*3ac0a46fSAndroid Build Coastguard Worker    self._RestoreLocalChanges()
161*3ac0a46fSAndroid Build Coastguard Worker
162*3ac0a46fSAndroid Build Coastguard Worker    return before, after
163*3ac0a46fSAndroid Build Coastguard Worker
164*3ac0a46fSAndroid Build Coastguard Worker  def _ProfileTwoOtherBranches(self, before_branch, after_branch):
165*3ac0a46fSAndroid Build Coastguard Worker    """Profiles two branches that are not the current branch.
166*3ac0a46fSAndroid Build Coastguard Worker
167*3ac0a46fSAndroid Build Coastguard Worker    This is done in new, cloned repositories, therefore it is safer but slower
168*3ac0a46fSAndroid Build Coastguard Worker    and requires downloads.
169*3ac0a46fSAndroid Build Coastguard Worker
170*3ac0a46fSAndroid Build Coastguard Worker    after_branch does not need to descend from before_branch, they will be
171*3ac0a46fSAndroid Build Coastguard Worker    measured the same way
172*3ac0a46fSAndroid Build Coastguard Worker
173*3ac0a46fSAndroid Build Coastguard Worker    Args:
174*3ac0a46fSAndroid Build Coastguard Worker      before_branch: One branch to profile.
175*3ac0a46fSAndroid Build Coastguard Worker      after_branch: Other branch to profile.
176*3ac0a46fSAndroid Build Coastguard Worker
177*3ac0a46fSAndroid Build Coastguard Worker    Returns:
178*3ac0a46fSAndroid Build Coastguard Worker      A tuple (before, after), where each of before and after is a dict
179*3ac0a46fSAndroid Build Coastguard Worker      mapping a test case name to the profiling values for that test case
180*3ac0a46fSAndroid Build Coastguard Worker      in the given branch.
181*3ac0a46fSAndroid Build Coastguard Worker    """
182*3ac0a46fSAndroid Build Coastguard Worker    after = self._ProfileSeparateRepo('after', self.after_build_dir,
183*3ac0a46fSAndroid Build Coastguard Worker                                      after_branch)
184*3ac0a46fSAndroid Build Coastguard Worker    before = self._ProfileSeparateRepo('before', self.before_build_dir,
185*3ac0a46fSAndroid Build Coastguard Worker                                       before_branch)
186*3ac0a46fSAndroid Build Coastguard Worker    return before, after
187*3ac0a46fSAndroid Build Coastguard Worker
188*3ac0a46fSAndroid Build Coastguard Worker  def _ProfileCurrentAndOtherBranchInThisRepo(self, other_branch):
189*3ac0a46fSAndroid Build Coastguard Worker    """Profiles the current branch (with uncommitted changes) and another one.
190*3ac0a46fSAndroid Build Coastguard Worker
191*3ac0a46fSAndroid Build Coastguard Worker    This is done in the local repository and changes may not be restored if the
192*3ac0a46fSAndroid Build Coastguard Worker    script fails or is interrupted.
193*3ac0a46fSAndroid Build Coastguard Worker
194*3ac0a46fSAndroid Build Coastguard Worker    The current branch does not need to descend from other_branch.
195*3ac0a46fSAndroid Build Coastguard Worker
196*3ac0a46fSAndroid Build Coastguard Worker    Args:
197*3ac0a46fSAndroid Build Coastguard Worker      other_branch: Other branch to profile that is not the current.
198*3ac0a46fSAndroid Build Coastguard Worker
199*3ac0a46fSAndroid Build Coastguard Worker    Returns:
200*3ac0a46fSAndroid Build Coastguard Worker      A tuple (before, after), where each of before and after is a dict
201*3ac0a46fSAndroid Build Coastguard Worker      mapping a test case name to the profiling values for that test case
202*3ac0a46fSAndroid Build Coastguard Worker      in the given branch. The current branch is considered to be "after" and
203*3ac0a46fSAndroid Build Coastguard Worker      the other branch is considered to be "before".
204*3ac0a46fSAndroid Build Coastguard Worker    """
205*3ac0a46fSAndroid Build Coastguard Worker    branch_to_restore = self.git.GetCurrentBranchName()
206*3ac0a46fSAndroid Build Coastguard Worker
207*3ac0a46fSAndroid Build Coastguard Worker    self._BuildCurrentBranch(self.after_build_dir)
208*3ac0a46fSAndroid Build Coastguard Worker    after = self._MeasureCurrentBranch('after', self.after_build_dir)
209*3ac0a46fSAndroid Build Coastguard Worker
210*3ac0a46fSAndroid Build Coastguard Worker    self._StashLocalChanges()
211*3ac0a46fSAndroid Build Coastguard Worker
212*3ac0a46fSAndroid Build Coastguard Worker    self._CheckoutBranch(other_branch)
213*3ac0a46fSAndroid Build Coastguard Worker    self._BuildCurrentBranch(self.before_build_dir)
214*3ac0a46fSAndroid Build Coastguard Worker    before = self._MeasureCurrentBranch('before', self.before_build_dir)
215*3ac0a46fSAndroid Build Coastguard Worker
216*3ac0a46fSAndroid Build Coastguard Worker    self._CheckoutBranch(branch_to_restore)
217*3ac0a46fSAndroid Build Coastguard Worker    self._RestoreLocalChanges()
218*3ac0a46fSAndroid Build Coastguard Worker
219*3ac0a46fSAndroid Build Coastguard Worker    return before, after
220*3ac0a46fSAndroid Build Coastguard Worker
221*3ac0a46fSAndroid Build Coastguard Worker  def _ProfileCurrentAndOtherBranch(self, other_branch):
222*3ac0a46fSAndroid Build Coastguard Worker    """Profiles the current branch (with uncommitted changes) and another one.
223*3ac0a46fSAndroid Build Coastguard Worker
224*3ac0a46fSAndroid Build Coastguard Worker    This is done in new, cloned repositories, therefore it is safer but slower
225*3ac0a46fSAndroid Build Coastguard Worker    and requires downloads.
226*3ac0a46fSAndroid Build Coastguard Worker
227*3ac0a46fSAndroid Build Coastguard Worker    The current branch does not need to descend from other_branch.
228*3ac0a46fSAndroid Build Coastguard Worker
229*3ac0a46fSAndroid Build Coastguard Worker    Args:
230*3ac0a46fSAndroid Build Coastguard Worker      other_branch: Other branch to profile that is not the current. None will
231*3ac0a46fSAndroid Build Coastguard Worker          compare to the same branch.
232*3ac0a46fSAndroid Build Coastguard Worker
233*3ac0a46fSAndroid Build Coastguard Worker    Returns:
234*3ac0a46fSAndroid Build Coastguard Worker      A tuple (before, after), where each of before and after is a dict
235*3ac0a46fSAndroid Build Coastguard Worker      mapping a test case name to the profiling values for that test case
236*3ac0a46fSAndroid Build Coastguard Worker      in the given branch. The current branch is considered to be "after" and
237*3ac0a46fSAndroid Build Coastguard Worker      the other branch is considered to be "before".
238*3ac0a46fSAndroid Build Coastguard Worker    """
239*3ac0a46fSAndroid Build Coastguard Worker    self._BuildCurrentBranch(self.after_build_dir)
240*3ac0a46fSAndroid Build Coastguard Worker    after = self._MeasureCurrentBranch('after', self.after_build_dir)
241*3ac0a46fSAndroid Build Coastguard Worker
242*3ac0a46fSAndroid Build Coastguard Worker    before = self._ProfileSeparateRepo('before', self.before_build_dir,
243*3ac0a46fSAndroid Build Coastguard Worker                                       other_branch)
244*3ac0a46fSAndroid Build Coastguard Worker
245*3ac0a46fSAndroid Build Coastguard Worker    return before, after
246*3ac0a46fSAndroid Build Coastguard Worker
247*3ac0a46fSAndroid Build Coastguard Worker  def _ProfileLocalChangesAndCurrentBranchInThisRepo(self):
248*3ac0a46fSAndroid Build Coastguard Worker    """Profiles the current branch with and without uncommitted changes.
249*3ac0a46fSAndroid Build Coastguard Worker
250*3ac0a46fSAndroid Build Coastguard Worker    This is done in the local repository and changes may not be restored if the
251*3ac0a46fSAndroid Build Coastguard Worker    script fails or is interrupted.
252*3ac0a46fSAndroid Build Coastguard Worker
253*3ac0a46fSAndroid Build Coastguard Worker    Returns:
254*3ac0a46fSAndroid Build Coastguard Worker      A tuple (before, after), where each of before and after is a dict
255*3ac0a46fSAndroid Build Coastguard Worker      mapping a test case name to the profiling values for that test case
256*3ac0a46fSAndroid Build Coastguard Worker      using the given version. The current branch without uncommitted changes is
257*3ac0a46fSAndroid Build Coastguard Worker      considered to be "before" and with uncommitted changes is considered to be
258*3ac0a46fSAndroid Build Coastguard Worker      "after".
259*3ac0a46fSAndroid Build Coastguard Worker    """
260*3ac0a46fSAndroid Build Coastguard Worker    self._BuildCurrentBranch(self.after_build_dir)
261*3ac0a46fSAndroid Build Coastguard Worker    after = self._MeasureCurrentBranch('after', self.after_build_dir)
262*3ac0a46fSAndroid Build Coastguard Worker
263*3ac0a46fSAndroid Build Coastguard Worker    pushed = self._StashLocalChanges()
264*3ac0a46fSAndroid Build Coastguard Worker    if not pushed and not self.args.build_dir_before:
265*3ac0a46fSAndroid Build Coastguard Worker      PrintErr('Warning: No local changes to compare')
266*3ac0a46fSAndroid Build Coastguard Worker
267*3ac0a46fSAndroid Build Coastguard Worker    before_build_dir = self.before_build_dir
268*3ac0a46fSAndroid Build Coastguard Worker
269*3ac0a46fSAndroid Build Coastguard Worker    self._BuildCurrentBranch(before_build_dir)
270*3ac0a46fSAndroid Build Coastguard Worker    before = self._MeasureCurrentBranch('before', before_build_dir)
271*3ac0a46fSAndroid Build Coastguard Worker
272*3ac0a46fSAndroid Build Coastguard Worker    self._RestoreLocalChanges()
273*3ac0a46fSAndroid Build Coastguard Worker
274*3ac0a46fSAndroid Build Coastguard Worker    return before, after
275*3ac0a46fSAndroid Build Coastguard Worker
276*3ac0a46fSAndroid Build Coastguard Worker  def _ProfileLocalChangesAndCurrentBranch(self):
277*3ac0a46fSAndroid Build Coastguard Worker    """Profiles the current branch with and without uncommitted changes.
278*3ac0a46fSAndroid Build Coastguard Worker
279*3ac0a46fSAndroid Build Coastguard Worker    This is done in new, cloned repositories, therefore it is safer but slower
280*3ac0a46fSAndroid Build Coastguard Worker    and requires downloads.
281*3ac0a46fSAndroid Build Coastguard Worker
282*3ac0a46fSAndroid Build Coastguard Worker    Returns:
283*3ac0a46fSAndroid Build Coastguard Worker      A tuple (before, after), where each of before and after is a dict
284*3ac0a46fSAndroid Build Coastguard Worker      mapping a test case name to the profiling values for that test case
285*3ac0a46fSAndroid Build Coastguard Worker      using the given version. The current branch without uncommitted changes is
286*3ac0a46fSAndroid Build Coastguard Worker      considered to be "before" and with uncommitted changes is considered to be
287*3ac0a46fSAndroid Build Coastguard Worker      "after".
288*3ac0a46fSAndroid Build Coastguard Worker    """
289*3ac0a46fSAndroid Build Coastguard Worker    return self._ProfileCurrentAndOtherBranch(other_branch=None)
290*3ac0a46fSAndroid Build Coastguard Worker
291*3ac0a46fSAndroid Build Coastguard Worker  def _ProfileSeparateRepo(self, run_label, relative_build_dir, branch):
292*3ac0a46fSAndroid Build Coastguard Worker    """Profiles a branch in a a temporary git repository.
293*3ac0a46fSAndroid Build Coastguard Worker
294*3ac0a46fSAndroid Build Coastguard Worker    Args:
295*3ac0a46fSAndroid Build Coastguard Worker      run_label: String to differentiate this version of the code in output
296*3ac0a46fSAndroid Build Coastguard Worker          files from other versions.
297*3ac0a46fSAndroid Build Coastguard Worker      relative_build_dir: Path to the build dir in the current working dir to
298*3ac0a46fSAndroid Build Coastguard Worker          clone build args from.
299*3ac0a46fSAndroid Build Coastguard Worker      branch: Branch to checkout in the new repository. None will
300*3ac0a46fSAndroid Build Coastguard Worker          profile the same branch checked out in the original repo.
301*3ac0a46fSAndroid Build Coastguard Worker    Returns:
302*3ac0a46fSAndroid Build Coastguard Worker      A dict mapping each test case name to the profiling values for that
303*3ac0a46fSAndroid Build Coastguard Worker      test case.
304*3ac0a46fSAndroid Build Coastguard Worker    """
305*3ac0a46fSAndroid Build Coastguard Worker    build_dir = self._CreateTempRepo('repo_%s' % run_label, relative_build_dir,
306*3ac0a46fSAndroid Build Coastguard Worker                                     branch)
307*3ac0a46fSAndroid Build Coastguard Worker
308*3ac0a46fSAndroid Build Coastguard Worker    self._BuildCurrentBranch(build_dir)
309*3ac0a46fSAndroid Build Coastguard Worker    return self._MeasureCurrentBranch(run_label, build_dir)
310*3ac0a46fSAndroid Build Coastguard Worker
311*3ac0a46fSAndroid Build Coastguard Worker  def _CreateTempRepo(self, dir_name, relative_build_dir, branch):
312*3ac0a46fSAndroid Build Coastguard Worker    """Clones a temporary git repository out of the current working dir.
313*3ac0a46fSAndroid Build Coastguard Worker
314*3ac0a46fSAndroid Build Coastguard Worker    Args:
315*3ac0a46fSAndroid Build Coastguard Worker      dir_name: Name for the temporary repository directory
316*3ac0a46fSAndroid Build Coastguard Worker      relative_build_dir: Path to the build dir in the current working dir to
317*3ac0a46fSAndroid Build Coastguard Worker          clone build args from.
318*3ac0a46fSAndroid Build Coastguard Worker      branch: Branch to checkout in the new repository. None will keep checked
319*3ac0a46fSAndroid Build Coastguard Worker          out the same branch as the local repo.
320*3ac0a46fSAndroid Build Coastguard Worker    Returns:
321*3ac0a46fSAndroid Build Coastguard Worker      Path to the build directory of the new repository.
322*3ac0a46fSAndroid Build Coastguard Worker    """
323*3ac0a46fSAndroid Build Coastguard Worker    cwd = os.getcwd()
324*3ac0a46fSAndroid Build Coastguard Worker
325*3ac0a46fSAndroid Build Coastguard Worker    repo_dir = tempfile.mkdtemp(suffix='-%s' % dir_name)
326*3ac0a46fSAndroid Build Coastguard Worker    src_dir = os.path.join(repo_dir, 'pdfium')
327*3ac0a46fSAndroid Build Coastguard Worker
328*3ac0a46fSAndroid Build Coastguard Worker    self.git.CloneLocal(os.getcwd(), src_dir)
329*3ac0a46fSAndroid Build Coastguard Worker
330*3ac0a46fSAndroid Build Coastguard Worker    if branch is not None:
331*3ac0a46fSAndroid Build Coastguard Worker      os.chdir(src_dir)
332*3ac0a46fSAndroid Build Coastguard Worker      self.git.Checkout(branch)
333*3ac0a46fSAndroid Build Coastguard Worker
334*3ac0a46fSAndroid Build Coastguard Worker    os.chdir(repo_dir)
335*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Syncing...')
336*3ac0a46fSAndroid Build Coastguard Worker
337*3ac0a46fSAndroid Build Coastguard Worker    cmd = [
338*3ac0a46fSAndroid Build Coastguard Worker        'gclient', 'config', '--unmanaged',
339*3ac0a46fSAndroid Build Coastguard Worker        'https://pdfium.googlesource.com/pdfium.git'
340*3ac0a46fSAndroid Build Coastguard Worker    ]
341*3ac0a46fSAndroid Build Coastguard Worker    if self.args.cache_dir:
342*3ac0a46fSAndroid Build Coastguard Worker      cmd.append('--cache-dir=%s' % self.args.cache_dir)
343*3ac0a46fSAndroid Build Coastguard Worker    RunCommandPropagateErr(cmd, exit_status_on_error=1)
344*3ac0a46fSAndroid Build Coastguard Worker
345*3ac0a46fSAndroid Build Coastguard Worker    RunCommandPropagateErr(['gclient', 'sync', '--force'],
346*3ac0a46fSAndroid Build Coastguard Worker                           exit_status_on_error=1)
347*3ac0a46fSAndroid Build Coastguard Worker
348*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Done.')
349*3ac0a46fSAndroid Build Coastguard Worker
350*3ac0a46fSAndroid Build Coastguard Worker    build_dir = os.path.join(src_dir, relative_build_dir)
351*3ac0a46fSAndroid Build Coastguard Worker    os.makedirs(build_dir)
352*3ac0a46fSAndroid Build Coastguard Worker    os.chdir(src_dir)
353*3ac0a46fSAndroid Build Coastguard Worker
354*3ac0a46fSAndroid Build Coastguard Worker    source_gn_args = os.path.join(cwd, relative_build_dir, 'args.gn')
355*3ac0a46fSAndroid Build Coastguard Worker    dest_gn_args = os.path.join(build_dir, 'args.gn')
356*3ac0a46fSAndroid Build Coastguard Worker    shutil.copy(source_gn_args, dest_gn_args)
357*3ac0a46fSAndroid Build Coastguard Worker
358*3ac0a46fSAndroid Build Coastguard Worker    RunCommandPropagateErr(['gn', 'gen', relative_build_dir],
359*3ac0a46fSAndroid Build Coastguard Worker                           exit_status_on_error=1)
360*3ac0a46fSAndroid Build Coastguard Worker
361*3ac0a46fSAndroid Build Coastguard Worker    os.chdir(cwd)
362*3ac0a46fSAndroid Build Coastguard Worker
363*3ac0a46fSAndroid Build Coastguard Worker    return build_dir
364*3ac0a46fSAndroid Build Coastguard Worker
365*3ac0a46fSAndroid Build Coastguard Worker  def _CheckoutBranch(self, branch):
366*3ac0a46fSAndroid Build Coastguard Worker    PrintErr("Checking out branch '%s'" % branch)
367*3ac0a46fSAndroid Build Coastguard Worker    self.git.Checkout(branch)
368*3ac0a46fSAndroid Build Coastguard Worker
369*3ac0a46fSAndroid Build Coastguard Worker  def _StashLocalChanges(self):
370*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Stashing local changes')
371*3ac0a46fSAndroid Build Coastguard Worker    return self.git.StashPush()
372*3ac0a46fSAndroid Build Coastguard Worker
373*3ac0a46fSAndroid Build Coastguard Worker  def _RestoreLocalChanges(self):
374*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Restoring local changes')
375*3ac0a46fSAndroid Build Coastguard Worker    self.git.StashPopAll()
376*3ac0a46fSAndroid Build Coastguard Worker
377*3ac0a46fSAndroid Build Coastguard Worker  def _BuildCurrentBranch(self, build_dir):
378*3ac0a46fSAndroid Build Coastguard Worker    """Synchronizes and builds the current version of pdfium.
379*3ac0a46fSAndroid Build Coastguard Worker
380*3ac0a46fSAndroid Build Coastguard Worker    Args:
381*3ac0a46fSAndroid Build Coastguard Worker      build_dir: String with path to build directory
382*3ac0a46fSAndroid Build Coastguard Worker    """
383*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Syncing...')
384*3ac0a46fSAndroid Build Coastguard Worker    RunCommandPropagateErr(['gclient', 'sync', '--force'],
385*3ac0a46fSAndroid Build Coastguard Worker                           exit_status_on_error=1)
386*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Done.')
387*3ac0a46fSAndroid Build Coastguard Worker
388*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Building...')
389*3ac0a46fSAndroid Build Coastguard Worker    cmd = ['ninja', '-C', build_dir, 'pdfium_test']
390*3ac0a46fSAndroid Build Coastguard Worker    if GetBooleanGnArg('use_goma', build_dir):
391*3ac0a46fSAndroid Build Coastguard Worker      cmd.extend(['-j', '250'])
392*3ac0a46fSAndroid Build Coastguard Worker    RunCommandPropagateErr(cmd, stdout_has_errors=True, exit_status_on_error=1)
393*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Done.')
394*3ac0a46fSAndroid Build Coastguard Worker
395*3ac0a46fSAndroid Build Coastguard Worker  def _MeasureCurrentBranch(self, run_label, build_dir):
396*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Measuring...')
397*3ac0a46fSAndroid Build Coastguard Worker    if self.args.num_workers > 1 and len(self.test_cases) > 1:
398*3ac0a46fSAndroid Build Coastguard Worker      results = self._RunAsync(run_label, build_dir)
399*3ac0a46fSAndroid Build Coastguard Worker    else:
400*3ac0a46fSAndroid Build Coastguard Worker      results = self._RunSync(run_label, build_dir)
401*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Done.')
402*3ac0a46fSAndroid Build Coastguard Worker
403*3ac0a46fSAndroid Build Coastguard Worker    return results
404*3ac0a46fSAndroid Build Coastguard Worker
405*3ac0a46fSAndroid Build Coastguard Worker  def _RunSync(self, run_label, build_dir):
406*3ac0a46fSAndroid Build Coastguard Worker    """Profiles the test cases synchronously.
407*3ac0a46fSAndroid Build Coastguard Worker
408*3ac0a46fSAndroid Build Coastguard Worker    Args:
409*3ac0a46fSAndroid Build Coastguard Worker      run_label: String to differentiate this version of the code in output
410*3ac0a46fSAndroid Build Coastguard Worker          files from other versions.
411*3ac0a46fSAndroid Build Coastguard Worker      build_dir: String with path to build directory
412*3ac0a46fSAndroid Build Coastguard Worker
413*3ac0a46fSAndroid Build Coastguard Worker    Returns:
414*3ac0a46fSAndroid Build Coastguard Worker      A dict mapping each test case name to the profiling values for that
415*3ac0a46fSAndroid Build Coastguard Worker      test case.
416*3ac0a46fSAndroid Build Coastguard Worker    """
417*3ac0a46fSAndroid Build Coastguard Worker    results = {}
418*3ac0a46fSAndroid Build Coastguard Worker
419*3ac0a46fSAndroid Build Coastguard Worker    for test_case in self.test_cases:
420*3ac0a46fSAndroid Build Coastguard Worker      result = self.RunSingleTestCase(run_label, build_dir, test_case)
421*3ac0a46fSAndroid Build Coastguard Worker      if result is not None:
422*3ac0a46fSAndroid Build Coastguard Worker        results[test_case] = result
423*3ac0a46fSAndroid Build Coastguard Worker
424*3ac0a46fSAndroid Build Coastguard Worker    return results
425*3ac0a46fSAndroid Build Coastguard Worker
426*3ac0a46fSAndroid Build Coastguard Worker  def _RunAsync(self, run_label, build_dir):
427*3ac0a46fSAndroid Build Coastguard Worker    """Profiles the test cases asynchronously.
428*3ac0a46fSAndroid Build Coastguard Worker
429*3ac0a46fSAndroid Build Coastguard Worker    Uses as many workers as configured by --num-workers.
430*3ac0a46fSAndroid Build Coastguard Worker
431*3ac0a46fSAndroid Build Coastguard Worker    Args:
432*3ac0a46fSAndroid Build Coastguard Worker      run_label: String to differentiate this version of the code in output
433*3ac0a46fSAndroid Build Coastguard Worker          files from other versions.
434*3ac0a46fSAndroid Build Coastguard Worker      build_dir: String with path to build directory
435*3ac0a46fSAndroid Build Coastguard Worker
436*3ac0a46fSAndroid Build Coastguard Worker    Returns:
437*3ac0a46fSAndroid Build Coastguard Worker      A dict mapping each test case name to the profiling values for that
438*3ac0a46fSAndroid Build Coastguard Worker      test case.
439*3ac0a46fSAndroid Build Coastguard Worker    """
440*3ac0a46fSAndroid Build Coastguard Worker    results = {}
441*3ac0a46fSAndroid Build Coastguard Worker    pool = multiprocessing.Pool(self.args.num_workers)
442*3ac0a46fSAndroid Build Coastguard Worker    worker_func = functools.partial(RunSingleTestCaseParallel, self, run_label,
443*3ac0a46fSAndroid Build Coastguard Worker                                    build_dir)
444*3ac0a46fSAndroid Build Coastguard Worker
445*3ac0a46fSAndroid Build Coastguard Worker    try:
446*3ac0a46fSAndroid Build Coastguard Worker      # The timeout is a workaround for http://bugs.python.org/issue8296
447*3ac0a46fSAndroid Build Coastguard Worker      # which prevents KeyboardInterrupt from working.
448*3ac0a46fSAndroid Build Coastguard Worker      one_year_in_seconds = 3600 * 24 * 365
449*3ac0a46fSAndroid Build Coastguard Worker      worker_results = (
450*3ac0a46fSAndroid Build Coastguard Worker          pool.map_async(worker_func, self.test_cases).get(one_year_in_seconds))
451*3ac0a46fSAndroid Build Coastguard Worker      for worker_result in worker_results:
452*3ac0a46fSAndroid Build Coastguard Worker        test_case, result = worker_result
453*3ac0a46fSAndroid Build Coastguard Worker        if result is not None:
454*3ac0a46fSAndroid Build Coastguard Worker          results[test_case] = result
455*3ac0a46fSAndroid Build Coastguard Worker    except KeyboardInterrupt:
456*3ac0a46fSAndroid Build Coastguard Worker      pool.terminate()
457*3ac0a46fSAndroid Build Coastguard Worker      sys.exit(1)
458*3ac0a46fSAndroid Build Coastguard Worker    else:
459*3ac0a46fSAndroid Build Coastguard Worker      pool.close()
460*3ac0a46fSAndroid Build Coastguard Worker
461*3ac0a46fSAndroid Build Coastguard Worker    pool.join()
462*3ac0a46fSAndroid Build Coastguard Worker
463*3ac0a46fSAndroid Build Coastguard Worker    return results
464*3ac0a46fSAndroid Build Coastguard Worker
465*3ac0a46fSAndroid Build Coastguard Worker  def RunSingleTestCase(self, run_label, build_dir, test_case):
466*3ac0a46fSAndroid Build Coastguard Worker    """Profiles a single test case.
467*3ac0a46fSAndroid Build Coastguard Worker
468*3ac0a46fSAndroid Build Coastguard Worker    Args:
469*3ac0a46fSAndroid Build Coastguard Worker      run_label: String to differentiate this version of the code in output
470*3ac0a46fSAndroid Build Coastguard Worker          files from other versions.
471*3ac0a46fSAndroid Build Coastguard Worker      build_dir: String with path to build directory
472*3ac0a46fSAndroid Build Coastguard Worker      test_case: Path to the test case.
473*3ac0a46fSAndroid Build Coastguard Worker
474*3ac0a46fSAndroid Build Coastguard Worker    Returns:
475*3ac0a46fSAndroid Build Coastguard Worker      The measured profiling value for that test case.
476*3ac0a46fSAndroid Build Coastguard Worker    """
477*3ac0a46fSAndroid Build Coastguard Worker    command = [
478*3ac0a46fSAndroid Build Coastguard Worker        self.safe_measure_script_path, test_case,
479*3ac0a46fSAndroid Build Coastguard Worker        '--build-dir=%s' % build_dir
480*3ac0a46fSAndroid Build Coastguard Worker    ]
481*3ac0a46fSAndroid Build Coastguard Worker
482*3ac0a46fSAndroid Build Coastguard Worker    if self.args.interesting_section:
483*3ac0a46fSAndroid Build Coastguard Worker      command.append('--interesting-section')
484*3ac0a46fSAndroid Build Coastguard Worker
485*3ac0a46fSAndroid Build Coastguard Worker    if self.args.profiler:
486*3ac0a46fSAndroid Build Coastguard Worker      command.append('--profiler=%s' % self.args.profiler)
487*3ac0a46fSAndroid Build Coastguard Worker
488*3ac0a46fSAndroid Build Coastguard Worker    profile_file_path = self._GetProfileFilePath(run_label, test_case)
489*3ac0a46fSAndroid Build Coastguard Worker    if profile_file_path:
490*3ac0a46fSAndroid Build Coastguard Worker      command.append('--output-path=%s' % profile_file_path)
491*3ac0a46fSAndroid Build Coastguard Worker
492*3ac0a46fSAndroid Build Coastguard Worker    if self.args.png_dir:
493*3ac0a46fSAndroid Build Coastguard Worker      command.append('--png')
494*3ac0a46fSAndroid Build Coastguard Worker
495*3ac0a46fSAndroid Build Coastguard Worker    if self.args.pages:
496*3ac0a46fSAndroid Build Coastguard Worker      command.extend(['--pages', self.args.pages])
497*3ac0a46fSAndroid Build Coastguard Worker
498*3ac0a46fSAndroid Build Coastguard Worker    output = RunCommandPropagateErr(command)
499*3ac0a46fSAndroid Build Coastguard Worker
500*3ac0a46fSAndroid Build Coastguard Worker    if output is None:
501*3ac0a46fSAndroid Build Coastguard Worker      return None
502*3ac0a46fSAndroid Build Coastguard Worker
503*3ac0a46fSAndroid Build Coastguard Worker    if self.args.png_dir:
504*3ac0a46fSAndroid Build Coastguard Worker      self._MoveImages(test_case, run_label)
505*3ac0a46fSAndroid Build Coastguard Worker
506*3ac0a46fSAndroid Build Coastguard Worker    # Get the time number as output, making sure it's just a number
507*3ac0a46fSAndroid Build Coastguard Worker    output = output.strip()
508*3ac0a46fSAndroid Build Coastguard Worker    if re.match('^[0-9]+$', output):
509*3ac0a46fSAndroid Build Coastguard Worker      return int(output)
510*3ac0a46fSAndroid Build Coastguard Worker
511*3ac0a46fSAndroid Build Coastguard Worker    return None
512*3ac0a46fSAndroid Build Coastguard Worker
513*3ac0a46fSAndroid Build Coastguard Worker  def _MoveImages(self, test_case, run_label):
514*3ac0a46fSAndroid Build Coastguard Worker    png_dir = os.path.join(self.args.png_dir, run_label)
515*3ac0a46fSAndroid Build Coastguard Worker    if not os.path.exists(png_dir):
516*3ac0a46fSAndroid Build Coastguard Worker      os.makedirs(png_dir)
517*3ac0a46fSAndroid Build Coastguard Worker
518*3ac0a46fSAndroid Build Coastguard Worker    test_case_dir, test_case_filename = os.path.split(test_case)
519*3ac0a46fSAndroid Build Coastguard Worker    test_case_png_matcher = '%s.*.png' % test_case_filename
520*3ac0a46fSAndroid Build Coastguard Worker    for output_png in glob.glob(
521*3ac0a46fSAndroid Build Coastguard Worker        os.path.join(test_case_dir, test_case_png_matcher)):
522*3ac0a46fSAndroid Build Coastguard Worker      shutil.move(output_png, png_dir)
523*3ac0a46fSAndroid Build Coastguard Worker
524*3ac0a46fSAndroid Build Coastguard Worker  def _GetProfileFilePath(self, run_label, test_case):
525*3ac0a46fSAndroid Build Coastguard Worker    if self.args.output_dir:
526*3ac0a46fSAndroid Build Coastguard Worker      output_filename = (
527*3ac0a46fSAndroid Build Coastguard Worker          'callgrind.out.%s.%s' % (test_case.replace('/', '_'), run_label))
528*3ac0a46fSAndroid Build Coastguard Worker      return os.path.join(self.args.output_dir, output_filename)
529*3ac0a46fSAndroid Build Coastguard Worker    return None
530*3ac0a46fSAndroid Build Coastguard Worker
531*3ac0a46fSAndroid Build Coastguard Worker  def _DrawConclusions(self, times_before_branch, times_after_branch):
532*3ac0a46fSAndroid Build Coastguard Worker    """Draws conclusions comparing results of test runs in two branches.
533*3ac0a46fSAndroid Build Coastguard Worker
534*3ac0a46fSAndroid Build Coastguard Worker    Args:
535*3ac0a46fSAndroid Build Coastguard Worker      times_before_branch: A dict mapping each test case name to the
536*3ac0a46fSAndroid Build Coastguard Worker          profiling values for that test case in the branch to be considered
537*3ac0a46fSAndroid Build Coastguard Worker          as the baseline.
538*3ac0a46fSAndroid Build Coastguard Worker      times_after_branch: A dict mapping each test case name to the
539*3ac0a46fSAndroid Build Coastguard Worker          profiling values for that test case in the branch to be considered
540*3ac0a46fSAndroid Build Coastguard Worker          as the new version.
541*3ac0a46fSAndroid Build Coastguard Worker
542*3ac0a46fSAndroid Build Coastguard Worker    Returns:
543*3ac0a46fSAndroid Build Coastguard Worker      ComparisonConclusions with all test cases processed.
544*3ac0a46fSAndroid Build Coastguard Worker    """
545*3ac0a46fSAndroid Build Coastguard Worker    conclusions = ComparisonConclusions(self.args.threshold_significant)
546*3ac0a46fSAndroid Build Coastguard Worker
547*3ac0a46fSAndroid Build Coastguard Worker    for test_case in sorted(self.test_cases):
548*3ac0a46fSAndroid Build Coastguard Worker      before = times_before_branch.get(test_case)
549*3ac0a46fSAndroid Build Coastguard Worker      after = times_after_branch.get(test_case)
550*3ac0a46fSAndroid Build Coastguard Worker      conclusions.ProcessCase(test_case, before, after)
551*3ac0a46fSAndroid Build Coastguard Worker
552*3ac0a46fSAndroid Build Coastguard Worker    return conclusions
553*3ac0a46fSAndroid Build Coastguard Worker
554*3ac0a46fSAndroid Build Coastguard Worker  def _PrintConclusions(self, conclusions_dict):
555*3ac0a46fSAndroid Build Coastguard Worker    """Prints the conclusions as the script output.
556*3ac0a46fSAndroid Build Coastguard Worker
557*3ac0a46fSAndroid Build Coastguard Worker    Depending on the script args, this can output a human or a machine-readable
558*3ac0a46fSAndroid Build Coastguard Worker    version of the conclusions.
559*3ac0a46fSAndroid Build Coastguard Worker
560*3ac0a46fSAndroid Build Coastguard Worker    Args:
561*3ac0a46fSAndroid Build Coastguard Worker      conclusions_dict: Dict to print returned from
562*3ac0a46fSAndroid Build Coastguard Worker          ComparisonConclusions.GetOutputDict().
563*3ac0a46fSAndroid Build Coastguard Worker    """
564*3ac0a46fSAndroid Build Coastguard Worker    if self.args.machine_readable:
565*3ac0a46fSAndroid Build Coastguard Worker      print(json.dumps(conclusions_dict))
566*3ac0a46fSAndroid Build Coastguard Worker    else:
567*3ac0a46fSAndroid Build Coastguard Worker      PrintConclusionsDictHumanReadable(
568*3ac0a46fSAndroid Build Coastguard Worker          conclusions_dict, colored=True, key=self.args.case_order)
569*3ac0a46fSAndroid Build Coastguard Worker
570*3ac0a46fSAndroid Build Coastguard Worker  def _CleanUp(self, conclusions):
571*3ac0a46fSAndroid Build Coastguard Worker    """Removes profile output files for uninteresting cases.
572*3ac0a46fSAndroid Build Coastguard Worker
573*3ac0a46fSAndroid Build Coastguard Worker    Cases without significant regressions or improvements and considered
574*3ac0a46fSAndroid Build Coastguard Worker    uninteresting.
575*3ac0a46fSAndroid Build Coastguard Worker
576*3ac0a46fSAndroid Build Coastguard Worker    Args:
577*3ac0a46fSAndroid Build Coastguard Worker      conclusions: A ComparisonConclusions.
578*3ac0a46fSAndroid Build Coastguard Worker    """
579*3ac0a46fSAndroid Build Coastguard Worker    if not self.args.output_dir:
580*3ac0a46fSAndroid Build Coastguard Worker      return
581*3ac0a46fSAndroid Build Coastguard Worker
582*3ac0a46fSAndroid Build Coastguard Worker    if self.args.profiler != 'callgrind':
583*3ac0a46fSAndroid Build Coastguard Worker      return
584*3ac0a46fSAndroid Build Coastguard Worker
585*3ac0a46fSAndroid Build Coastguard Worker    for case_result in conclusions.GetCaseResults().values():
586*3ac0a46fSAndroid Build Coastguard Worker      if case_result.rating not in [RATING_REGRESSION, RATING_IMPROVEMENT]:
587*3ac0a46fSAndroid Build Coastguard Worker        self._CleanUpOutputFile('before', case_result.case_name)
588*3ac0a46fSAndroid Build Coastguard Worker        self._CleanUpOutputFile('after', case_result.case_name)
589*3ac0a46fSAndroid Build Coastguard Worker
590*3ac0a46fSAndroid Build Coastguard Worker  def _CleanUpOutputFile(self, run_label, case_name):
591*3ac0a46fSAndroid Build Coastguard Worker    """Removes one profile output file.
592*3ac0a46fSAndroid Build Coastguard Worker
593*3ac0a46fSAndroid Build Coastguard Worker    If the output file does not exist, fails silently.
594*3ac0a46fSAndroid Build Coastguard Worker
595*3ac0a46fSAndroid Build Coastguard Worker    Args:
596*3ac0a46fSAndroid Build Coastguard Worker      run_label: String to differentiate a version of the code in output
597*3ac0a46fSAndroid Build Coastguard Worker          files from other versions.
598*3ac0a46fSAndroid Build Coastguard Worker      case_name: String identifying test case for which to remove the output
599*3ac0a46fSAndroid Build Coastguard Worker          file.
600*3ac0a46fSAndroid Build Coastguard Worker    """
601*3ac0a46fSAndroid Build Coastguard Worker    try:
602*3ac0a46fSAndroid Build Coastguard Worker      os.remove(self._GetProfileFilePath(run_label, case_name))
603*3ac0a46fSAndroid Build Coastguard Worker    except OSError:
604*3ac0a46fSAndroid Build Coastguard Worker      pass
605*3ac0a46fSAndroid Build Coastguard Worker
606*3ac0a46fSAndroid Build Coastguard Worker
607*3ac0a46fSAndroid Build Coastguard Workerdef main():
608*3ac0a46fSAndroid Build Coastguard Worker  parser = argparse.ArgumentParser()
609*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
610*3ac0a46fSAndroid Build Coastguard Worker      'input_paths',
611*3ac0a46fSAndroid Build Coastguard Worker      nargs='+',
612*3ac0a46fSAndroid Build Coastguard Worker      help='pdf files or directories to search for pdf files '
613*3ac0a46fSAndroid Build Coastguard Worker      'to run as test cases')
614*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
615*3ac0a46fSAndroid Build Coastguard Worker      '--branch-before',
616*3ac0a46fSAndroid Build Coastguard Worker      help='git branch to use as "before" for comparison. '
617*3ac0a46fSAndroid Build Coastguard Worker      'Omitting this will use the current branch '
618*3ac0a46fSAndroid Build Coastguard Worker      'without uncommitted changes as the baseline.')
619*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
620*3ac0a46fSAndroid Build Coastguard Worker      '--branch-after',
621*3ac0a46fSAndroid Build Coastguard Worker      help='git branch to use as "after" for comparison. '
622*3ac0a46fSAndroid Build Coastguard Worker      'Omitting this will use the current branch '
623*3ac0a46fSAndroid Build Coastguard Worker      'with uncommitted changes.')
624*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
625*3ac0a46fSAndroid Build Coastguard Worker      '--build-dir',
626*3ac0a46fSAndroid Build Coastguard Worker      default=os.path.join('out', 'Release'),
627*3ac0a46fSAndroid Build Coastguard Worker      help='relative path from the base source directory '
628*3ac0a46fSAndroid Build Coastguard Worker      'to the build directory')
629*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
630*3ac0a46fSAndroid Build Coastguard Worker      '--build-dir-before',
631*3ac0a46fSAndroid Build Coastguard Worker      help='relative path from the base source directory '
632*3ac0a46fSAndroid Build Coastguard Worker      'to the build directory for the "before" branch, if '
633*3ac0a46fSAndroid Build Coastguard Worker      'different from the build directory for the '
634*3ac0a46fSAndroid Build Coastguard Worker      '"after" branch')
635*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
636*3ac0a46fSAndroid Build Coastguard Worker      '--cache-dir',
637*3ac0a46fSAndroid Build Coastguard Worker      default=None,
638*3ac0a46fSAndroid Build Coastguard Worker      help='directory with a new or preexisting cache for '
639*3ac0a46fSAndroid Build Coastguard Worker      'downloads. Default is to not use a cache.')
640*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
641*3ac0a46fSAndroid Build Coastguard Worker      '--this-repo',
642*3ac0a46fSAndroid Build Coastguard Worker      action='store_true',
643*3ac0a46fSAndroid Build Coastguard Worker      help='use the repository where the script is instead of '
644*3ac0a46fSAndroid Build Coastguard Worker      'checking out a temporary one. This is faster and '
645*3ac0a46fSAndroid Build Coastguard Worker      'does not require downloads, but although it '
646*3ac0a46fSAndroid Build Coastguard Worker      'restores the state of the local repo, if the '
647*3ac0a46fSAndroid Build Coastguard Worker      'script is killed or crashes the changes can remain '
648*3ac0a46fSAndroid Build Coastguard Worker      'stashed and you may be on another branch.')
649*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
650*3ac0a46fSAndroid Build Coastguard Worker      '--profiler',
651*3ac0a46fSAndroid Build Coastguard Worker      default='callgrind',
652*3ac0a46fSAndroid Build Coastguard Worker      help='which profiler to use. Supports callgrind, '
653*3ac0a46fSAndroid Build Coastguard Worker      'perfstat, and none. Default is callgrind.')
654*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
655*3ac0a46fSAndroid Build Coastguard Worker      '--interesting-section',
656*3ac0a46fSAndroid Build Coastguard Worker      action='store_true',
657*3ac0a46fSAndroid Build Coastguard Worker      help='whether to measure just the interesting section or '
658*3ac0a46fSAndroid Build Coastguard Worker      'the whole test harness. Limiting to only the '
659*3ac0a46fSAndroid Build Coastguard Worker      'interesting section does not work on Release since '
660*3ac0a46fSAndroid Build Coastguard Worker      'the delimiters are optimized out')
661*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
662*3ac0a46fSAndroid Build Coastguard Worker      '--pages',
663*3ac0a46fSAndroid Build Coastguard Worker      help='selects some pages to be rendered. Page numbers '
664*3ac0a46fSAndroid Build Coastguard Worker      'are 0-based. "--pages A" will render only page A. '
665*3ac0a46fSAndroid Build Coastguard Worker      '"--pages A-B" will render pages A to B '
666*3ac0a46fSAndroid Build Coastguard Worker      '(inclusive).')
667*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
668*3ac0a46fSAndroid Build Coastguard Worker      '--num-workers',
669*3ac0a46fSAndroid Build Coastguard Worker      default=multiprocessing.cpu_count(),
670*3ac0a46fSAndroid Build Coastguard Worker      type=int,
671*3ac0a46fSAndroid Build Coastguard Worker      help='run NUM_WORKERS jobs in parallel')
672*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
673*3ac0a46fSAndroid Build Coastguard Worker      '--output-dir', help='directory to write the profile data output files')
674*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
675*3ac0a46fSAndroid Build Coastguard Worker      '--png-dir',
676*3ac0a46fSAndroid Build Coastguard Worker      default=None,
677*3ac0a46fSAndroid Build Coastguard Worker      help='outputs pngs to the specified directory that can '
678*3ac0a46fSAndroid Build Coastguard Worker      'be compared with a static html generated. Will '
679*3ac0a46fSAndroid Build Coastguard Worker      'affect performance measurements.')
680*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
681*3ac0a46fSAndroid Build Coastguard Worker      '--png-threshold',
682*3ac0a46fSAndroid Build Coastguard Worker      default=0.0,
683*3ac0a46fSAndroid Build Coastguard Worker      type=float,
684*3ac0a46fSAndroid Build Coastguard Worker      help='Requires --png-dir. Threshold above which a png '
685*3ac0a46fSAndroid Build Coastguard Worker      'is considered to have changed.')
686*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
687*3ac0a46fSAndroid Build Coastguard Worker      '--threshold-significant',
688*3ac0a46fSAndroid Build Coastguard Worker      default=0.02,
689*3ac0a46fSAndroid Build Coastguard Worker      type=float,
690*3ac0a46fSAndroid Build Coastguard Worker      help='variations in performance above this factor are '
691*3ac0a46fSAndroid Build Coastguard Worker      'considered significant')
692*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
693*3ac0a46fSAndroid Build Coastguard Worker      '--machine-readable',
694*3ac0a46fSAndroid Build Coastguard Worker      action='store_true',
695*3ac0a46fSAndroid Build Coastguard Worker      help='whether to get output for machines. If enabled the '
696*3ac0a46fSAndroid Build Coastguard Worker      'output will be a json with the format specified in '
697*3ac0a46fSAndroid Build Coastguard Worker      'ComparisonConclusions.GetOutputDict(). Default is '
698*3ac0a46fSAndroid Build Coastguard Worker      'human-readable.')
699*3ac0a46fSAndroid Build Coastguard Worker  parser.add_argument(
700*3ac0a46fSAndroid Build Coastguard Worker      '--case-order',
701*3ac0a46fSAndroid Build Coastguard Worker      default=None,
702*3ac0a46fSAndroid Build Coastguard Worker      help='what key to use when sorting test cases in the '
703*3ac0a46fSAndroid Build Coastguard Worker      'output. Accepted values are "after", "before", '
704*3ac0a46fSAndroid Build Coastguard Worker      '"ratio" and "rating". Default is sorting by test '
705*3ac0a46fSAndroid Build Coastguard Worker      'case path.')
706*3ac0a46fSAndroid Build Coastguard Worker
707*3ac0a46fSAndroid Build Coastguard Worker  args = parser.parse_args()
708*3ac0a46fSAndroid Build Coastguard Worker
709*3ac0a46fSAndroid Build Coastguard Worker  # Always start at the pdfium src dir, which is assumed to be two level above
710*3ac0a46fSAndroid Build Coastguard Worker  # this script.
711*3ac0a46fSAndroid Build Coastguard Worker  pdfium_src_dir = os.path.join(
712*3ac0a46fSAndroid Build Coastguard Worker      os.path.dirname(__file__), os.path.pardir, os.path.pardir)
713*3ac0a46fSAndroid Build Coastguard Worker  os.chdir(pdfium_src_dir)
714*3ac0a46fSAndroid Build Coastguard Worker
715*3ac0a46fSAndroid Build Coastguard Worker  git = GitHelper()
716*3ac0a46fSAndroid Build Coastguard Worker
717*3ac0a46fSAndroid Build Coastguard Worker  if args.branch_after and not args.branch_before:
718*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('--branch-after requires --branch-before to be specified.')
719*3ac0a46fSAndroid Build Coastguard Worker    return 1
720*3ac0a46fSAndroid Build Coastguard Worker
721*3ac0a46fSAndroid Build Coastguard Worker  if args.branch_after and not git.BranchExists(args.branch_after):
722*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Branch "%s" does not exist' % args.branch_after)
723*3ac0a46fSAndroid Build Coastguard Worker    return 1
724*3ac0a46fSAndroid Build Coastguard Worker
725*3ac0a46fSAndroid Build Coastguard Worker  if args.branch_before and not git.BranchExists(args.branch_before):
726*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('Branch "%s" does not exist' % args.branch_before)
727*3ac0a46fSAndroid Build Coastguard Worker    return 1
728*3ac0a46fSAndroid Build Coastguard Worker
729*3ac0a46fSAndroid Build Coastguard Worker  if args.output_dir:
730*3ac0a46fSAndroid Build Coastguard Worker    args.output_dir = os.path.expanduser(args.output_dir)
731*3ac0a46fSAndroid Build Coastguard Worker    if not os.path.isdir(args.output_dir):
732*3ac0a46fSAndroid Build Coastguard Worker      PrintErr('"%s" is not a directory' % args.output_dir)
733*3ac0a46fSAndroid Build Coastguard Worker      return 1
734*3ac0a46fSAndroid Build Coastguard Worker
735*3ac0a46fSAndroid Build Coastguard Worker  if args.png_dir:
736*3ac0a46fSAndroid Build Coastguard Worker    args.png_dir = os.path.expanduser(args.png_dir)
737*3ac0a46fSAndroid Build Coastguard Worker    if not os.path.isdir(args.png_dir):
738*3ac0a46fSAndroid Build Coastguard Worker      PrintErr('"%s" is not a directory' % args.png_dir)
739*3ac0a46fSAndroid Build Coastguard Worker      return 1
740*3ac0a46fSAndroid Build Coastguard Worker
741*3ac0a46fSAndroid Build Coastguard Worker  if args.threshold_significant <= 0.0:
742*3ac0a46fSAndroid Build Coastguard Worker    PrintErr('--threshold-significant should receive a positive float')
743*3ac0a46fSAndroid Build Coastguard Worker    return 1
744*3ac0a46fSAndroid Build Coastguard Worker
745*3ac0a46fSAndroid Build Coastguard Worker  if args.png_threshold:
746*3ac0a46fSAndroid Build Coastguard Worker    if not args.png_dir:
747*3ac0a46fSAndroid Build Coastguard Worker      PrintErr('--png-threshold requires --png-dir to be specified.')
748*3ac0a46fSAndroid Build Coastguard Worker      return 1
749*3ac0a46fSAndroid Build Coastguard Worker
750*3ac0a46fSAndroid Build Coastguard Worker    if args.png_threshold <= 0.0:
751*3ac0a46fSAndroid Build Coastguard Worker      PrintErr('--png-threshold should receive a positive float')
752*3ac0a46fSAndroid Build Coastguard Worker      return 1
753*3ac0a46fSAndroid Build Coastguard Worker
754*3ac0a46fSAndroid Build Coastguard Worker  if args.pages:
755*3ac0a46fSAndroid Build Coastguard Worker    if not re.match(r'^\d+(-\d+)?$', args.pages):
756*3ac0a46fSAndroid Build Coastguard Worker      PrintErr('Supported formats for --pages are "--pages 7" and '
757*3ac0a46fSAndroid Build Coastguard Worker               '"--pages 3-6"')
758*3ac0a46fSAndroid Build Coastguard Worker      return 1
759*3ac0a46fSAndroid Build Coastguard Worker
760*3ac0a46fSAndroid Build Coastguard Worker  run = CompareRun(args)
761*3ac0a46fSAndroid Build Coastguard Worker  return run.Run()
762*3ac0a46fSAndroid Build Coastguard Worker
763*3ac0a46fSAndroid Build Coastguard Worker
764*3ac0a46fSAndroid Build Coastguard Workerif __name__ == '__main__':
765*3ac0a46fSAndroid Build Coastguard Worker  sys.exit(main())
766