Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
safetynet_job.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# Copyright 2017 The PDFium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Looks for performance regressions on all pushes since the last run.
6
7Run this nightly to have a periodical check for performance regressions.
8
9Stores the results for each run and last checkpoint in a results directory.
10"""
11
12import argparse
13import datetime
14import json
15import os
16import sys
17
18from common import PrintWithTime
19from common import RunCommandPropagateErr
20from githelper import GitHelper
21from safetynet_conclusions import PrintConclusionsDictHumanReadable
22
23
25 """Context for a single run, including name and directory paths."""
26
27 def __init__(self, args):
28 self.datetime = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
29 self.results_dir = args.results_dir
30 self.last_revision_covered_file = os.path.join(self.results_dir,
31 'last_revision_covered')
32 self.run_output_dir = os.path.join(self.results_dir,
33 'profiles_%s' % self.datetime)
34 self.run_output_log_file = os.path.join(self.results_dir,
35 '%s.log' % self.datetime)
36
37
38class JobRun:
39 """A single run looking for regressions since the last one."""
40
41 def __init__(self, args, context):
42 """Constructor.
43
44 Args:
45 args: Namespace with arguments passed to the script.
46 context: JobContext for this run.
47 """
48 self.git = GitHelper()
49 self.args = args
50 self.context = context
51
52 def Run(self):
53 """Searches for regressions.
54
55 Will only write a checkpoint when first run, and on all subsequent runs
56 a comparison is done against the last checkpoint.
57
58 Returns:
59 Exit code for the script: 0 if no significant changes are found; 1 if
60 there was an error in the comparison; 3 if there was a regression; 4 if
61 there was an improvement and no regression.
62 """
63 pdfium_src_dir = os.path.join(
64 os.path.dirname(__file__), os.path.pardir, os.path.pardir)
65 os.chdir(pdfium_src_dir)
66
67 branch_to_restore = self.git.GetCurrentBranchName()
68
69 if not self.args.no_checkout:
70 self.git.FetchOriginMaster()
71 self.git.Checkout('origin/main')
72
73 # Make sure results dir exists
74 if not os.path.exists(self.context.results_dir):
75 os.makedirs(self.context.results_dir)
76
77 if not os.path.exists(self.context.last_revision_covered_file):
78 result = self._InitialRun()
79 else:
80 with open(self.context.last_revision_covered_file) as f:
81 last_revision_covered = f.read().strip()
82 result = self._IncrementalRun(last_revision_covered)
83
84 self.git.Checkout(branch_to_restore)
85 return result
86
87 def _InitialRun(self):
88 """Initial run, just write a checkpoint.
89
90 Returns:
91 Exit code for the script.
92 """
93 current = self.git.GetCurrentBranchHash()
94
95 PrintWithTime('Initial run, current is %s' % current)
96
97 self._WriteCheckpoint(current)
98
99 PrintWithTime('All set up, next runs will be incremental and perform '
100 'comparisons')
101 return 0
102
103 def _IncrementalRun(self, last_revision_covered):
104 """Incremental run, compare against last checkpoint and update it.
105
106 Args:
107 last_revision_covered: String with hash for last checkpoint.
108
109 Returns:
110 Exit code for the script.
111 """
112 current = self.git.GetCurrentBranchHash()
113
114 PrintWithTime('Incremental run, current is %s, last is %s' %
115 (current, last_revision_covered))
116
117 if not os.path.exists(self.context.run_output_dir):
118 os.makedirs(self.context.run_output_dir)
119
120 if current == last_revision_covered:
121 PrintWithTime('No changes seen, finishing job')
122 output_info = {
123 'metadata':
124 self._BuildRunMetadata(last_revision_covered, current, False)
125 }
126 self._WriteRawJson(output_info)
127 return 0
128
129 # Run compare
130 cmd = [
131 'testing/tools/safetynet_compare.py', '--this-repo',
132 '--machine-readable',
133 '--branch-before=%s' % last_revision_covered,
134 '--output-dir=%s' % self.context.run_output_dir
135 ]
136 cmd.extend(self.args.input_paths)
137
138 json_output = RunCommandPropagateErr(cmd)
139
140 if json_output is None:
141 return 1
142
143 output_info = json.loads(json_output)
144
145 run_metadata = self._BuildRunMetadata(last_revision_covered, current, True)
146 output_info.setdefault('metadata', {}).update(run_metadata)
147 self._WriteRawJson(output_info)
148
149 PrintConclusionsDictHumanReadable(
150 output_info,
151 colored=(not self.args.output_to_log and not self.args.no_color),
152 key='after')
153
154 status = 0
155
156 if output_info['summary']['improvement']:
157 PrintWithTime('Improvement detected.')
158 status = 4
159
160 if output_info['summary']['regression']:
161 PrintWithTime('Regression detected.')
162 status = 3
163
164 if status == 0:
165 PrintWithTime('Nothing detected.')
166
167 self._WriteCheckpoint(current)
168
169 return status
170
171 def _WriteRawJson(self, output_info):
172 json_output_file = os.path.join(self.context.run_output_dir, 'raw.json')
173 with open(json_output_file, 'w') as f:
174 json.dump(output_info, f)
175
176 def _BuildRunMetadata(self, revision_before, revision_after,
177 comparison_performed):
178 return {
179 'datetime': self.context.datetime,
180 'revision_before': revision_before,
181 'revision_after': revision_after,
182 'comparison_performed': comparison_performed,
183 }
184
185 def _WriteCheckpoint(self, checkpoint):
186 if not self.args.no_checkpoint:
187 with open(self.context.last_revision_covered_file, 'w') as f:
188 f.write(checkpoint + '\n')
189
190
191def main():
192 parser = argparse.ArgumentParser()
193 parser.add_argument('results_dir', help='where to write the job results')
194 parser.add_argument(
195 'input_paths',
196 nargs='+',
197 help='pdf files or directories to search for pdf files '
198 'to run as test cases')
199 parser.add_argument(
200 '--no-checkout',
201 action='store_true',
202 help='whether to skip checking out origin/main. Use '
203 'for script debugging.')
204 parser.add_argument(
205 '--no-checkpoint',
206 action='store_true',
207 help='whether to skip writing the new checkpoint. Use '
208 'for script debugging.')
209 parser.add_argument(
210 '--no-color',
211 action='store_true',
212 help='whether to write output without color escape '
213 'codes.')
214 parser.add_argument(
215 '--output-to-log',
216 action='store_true',
217 help='whether to write output to a log file')
218 args = parser.parse_args()
219
220 job_context = JobContext(args)
221
222 if args.output_to_log:
223 log_file = open(job_context.run_output_log_file, 'w')
224 sys.stdout = log_file
225 sys.stderr = log_file
226
227 run = JobRun(args, job_context)
228 result = run.Run()
229
230 if args.output_to_log:
231 log_file.close()
232
233 return result
234
235
236if __name__ == '__main__':
237 sys.exit(main())
_IncrementalRun(self, last_revision_covered)
_BuildRunMetadata(self, revision_before, revision_after, comparison_performed)
file open(QIODevice::ReadOnly)