Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(216)

Side by Side Diff: gm/rebaseline_server/compare_rendered_pictures.py

Issue 265793013: make compare_rendered_pictures process render_pictures's new JSON output format (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: add Eric's idea as comment Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 """ 3 """
4 Copyright 2014 Google Inc. 4 Copyright 2014 Google Inc.
5 5
6 Use of this source code is governed by a BSD-style license that can be 6 Use of this source code is governed by a BSD-style license that can be
7 found in the LICENSE file. 7 found in the LICENSE file.
8 8
9 Compare results of two render_pictures runs. 9 Compare results of two render_pictures runs.
10 """ 10 """
(...skipping 21 matching lines...) Expand all
32 TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY) 32 TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY)
33 if GM_DIRECTORY not in sys.path: 33 if GM_DIRECTORY not in sys.path:
34 sys.path.append(GM_DIRECTORY) 34 sys.path.append(GM_DIRECTORY)
35 import download_actuals 35 import download_actuals
36 import gm_json 36 import gm_json
37 import imagediffdb 37 import imagediffdb
38 import imagepair 38 import imagepair
39 import imagepairset 39 import imagepairset
40 import results 40 import results
41 41
42 # Characters we don't want popping up just anywhere within filenames.
43 DISALLOWED_FILEPATH_CHAR_REGEX = re.compile('[^\w\-]')
44
45 # URL under which all render_pictures images can be found in Google Storage. 42 # URL under which all render_pictures images can be found in Google Storage.
46 # TODO(epoger): Move this default value into 43 # TODO(epoger): Move this default value into
47 # https://skia.googlesource.com/buildbot/+/master/site_config/global_variables.j son 44 # https://skia.googlesource.com/buildbot/+/master/site_config/global_variables.j son
48 DEFAULT_IMAGE_BASE_URL = 'http://chromium-skia-gm.commondatastorage.googleapis.c om/render_pictures/images' 45 DEFAULT_IMAGE_BASE_URL = 'http://chromium-skia-gm.commondatastorage.googleapis.c om/render_pictures/images'
49 46
50 47
51 class RenderedPicturesComparisons(results.BaseComparisons): 48 class RenderedPicturesComparisons(results.BaseComparisons):
52 """Loads results from two different render_pictures runs into an ImagePairSet. 49 """Loads results from two different render_pictures runs into an ImagePairSet.
53 """ 50 """
54 51
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
90 Args: 87 Args:
91 actuals_root: root directory containing all render_pictures-generated 88 actuals_root: root directory containing all render_pictures-generated
92 JSON files 89 JSON files
93 subdirs: (string, string) tuple; pair of subdirectories within 90 subdirs: (string, string) tuple; pair of subdirectories within
94 actuals_root to compare 91 actuals_root to compare
95 """ 92 """
96 logging.info( 93 logging.info(
97 'Reading actual-results JSON files from %s subdirs within %s...' % ( 94 'Reading actual-results JSON files from %s subdirs within %s...' % (
98 subdirs, actuals_root)) 95 subdirs, actuals_root))
99 subdirA, subdirB = subdirs 96 subdirA, subdirB = subdirs
100 subdirA_builder_dicts = self._read_dicts_from_root( 97 subdirA_dicts = self._read_dicts_from_root(
101 os.path.join(actuals_root, subdirA)) 98 os.path.join(actuals_root, subdirA))
102 subdirB_builder_dicts = self._read_dicts_from_root( 99 subdirB_dicts = self._read_dicts_from_root(
103 os.path.join(actuals_root, subdirB)) 100 os.path.join(actuals_root, subdirB))
104 logging.info('Comparing subdirs %s and %s...' % (subdirA, subdirB)) 101 logging.info('Comparing subdirs %s and %s...' % (subdirA, subdirB))
105 102
106 all_image_pairs = imagepairset.ImagePairSet( 103 all_image_pairs = imagepairset.ImagePairSet(
107 descriptions=subdirs, 104 descriptions=subdirs,
108 diff_base_url=self._diff_base_url) 105 diff_base_url=self._diff_base_url)
109 failing_image_pairs = imagepairset.ImagePairSet( 106 failing_image_pairs = imagepairset.ImagePairSet(
110 descriptions=subdirs, 107 descriptions=subdirs,
111 diff_base_url=self._diff_base_url) 108 diff_base_url=self._diff_base_url)
112 109
113 all_image_pairs.ensure_extra_column_values_in_summary( 110 all_image_pairs.ensure_extra_column_values_in_summary(
114 column_id=results.KEY__EXTRACOLUMN__RESULT_TYPE, values=[ 111 column_id=results.KEY__EXTRACOLUMN__RESULT_TYPE, values=[
115 results.KEY__RESULT_TYPE__FAILED, 112 results.KEY__RESULT_TYPE__FAILED,
116 results.KEY__RESULT_TYPE__NOCOMPARISON, 113 results.KEY__RESULT_TYPE__NOCOMPARISON,
117 results.KEY__RESULT_TYPE__SUCCEEDED, 114 results.KEY__RESULT_TYPE__SUCCEEDED,
118 ]) 115 ])
119 failing_image_pairs.ensure_extra_column_values_in_summary( 116 failing_image_pairs.ensure_extra_column_values_in_summary(
120 column_id=results.KEY__EXTRACOLUMN__RESULT_TYPE, values=[ 117 column_id=results.KEY__EXTRACOLUMN__RESULT_TYPE, values=[
121 results.KEY__RESULT_TYPE__FAILED, 118 results.KEY__RESULT_TYPE__FAILED,
122 results.KEY__RESULT_TYPE__NOCOMPARISON, 119 results.KEY__RESULT_TYPE__NOCOMPARISON,
123 ]) 120 ])
124 121
125 builders = sorted(set(subdirA_builder_dicts.keys() + 122 common_dict_paths = sorted(set(subdirA_dicts.keys() + subdirB_dicts.keys()))
126 subdirB_builder_dicts.keys())) 123 num_common_dict_paths = len(common_dict_paths)
127 num_builders = len(builders) 124 dict_num = 0
128 builder_num = 0 125 for dict_path in common_dict_paths:
129 for builder in builders: 126 dict_num += 1
130 builder_num += 1 127 logging.info('Generating pixel diffs for dict #%d of %d, "%s"...' %
131 logging.info('Generating pixel diffs for builder #%d of %d, "%s"...' % 128 (dict_num, num_common_dict_paths, dict_path))
132 (builder_num, num_builders, builder)) 129 dictA = subdirA_dicts[dict_path]
133 # TODO(epoger): This will fail if we have results for this builder in 130 dictB = subdirB_dicts[dict_path]
134 # subdirA but not subdirB (or vice versa). 131 self._validate_dict_version(dictA)
135 subdirA_results = results.BaseComparisons.combine_subdicts( 132 self._validate_dict_version(dictB)
136 subdirA_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS]) 133 dictA_results = dictA[gm_json.JSONKEY_ACTUALRESULTS]
137 subdirB_results = results.BaseComparisons.combine_subdicts( 134 dictB_results = dictB[gm_json.JSONKEY_ACTUALRESULTS]
138 subdirB_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS]) 135 skp_names = sorted(set(dictA_results.keys() + dictB_results.keys()))
139 image_names = sorted(set(subdirA_results.keys() + 136 for skp_name in skp_names:
140 subdirB_results.keys())) 137 imagepairs_for_this_skp = []
141 for image_name in image_names:
142 # The image name may contain funny characters or be ridiculously long
143 # (see https://code.google.com/p/skia/issues/detail?id=2344#c10 ),
144 # so make sure we sanitize it before using it in a URL path.
145 #
146 # TODO(epoger): Rather than sanitizing/truncating the image name here,
147 # do it in render_pictures instead.
148 # Reason: we will need to be consistent in applying this rule, so that
149 # the process which uploads the files to GS using these paths will
150 # match the paths created by downstream processes.
151 # So, we should make render_pictures write out images to paths that are
152 # "ready to upload" to Google Storage, like gm does.
153 sanitized_test_name = DISALLOWED_FILEPATH_CHAR_REGEX.sub(
154 '_', image_name)[:30]
155 138
156 subdirA_image_relative_url = ( 139 whole_image_A = RenderedPicturesComparisons.get_multilevel(
157 results.BaseComparisons._create_relative_url( 140 dictA_results, skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE)
158 hashtype_and_digest=subdirA_results.get(image_name), 141 whole_image_B = RenderedPicturesComparisons.get_multilevel(
159 test_name=sanitized_test_name)) 142 dictB_results, skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE)
160 subdirB_image_relative_url = ( 143 imagepairs_for_this_skp.append(self._create_image_pair(
161 results.BaseComparisons._create_relative_url( 144 test=skp_name, config=gm_json.JSONKEY_SOURCE_WHOLEIMAGE,
162 hashtype_and_digest=subdirB_results.get(image_name), 145 image_dict_A=whole_image_A, image_dict_B=whole_image_B))
163 test_name=sanitized_test_name))
164 146
165 # If we have images for at least one of these two subdirs, 147 tiled_images_A = RenderedPicturesComparisons.get_multilevel(
166 # add them to our list. 148 dictA_results, skp_name, gm_json.JSONKEY_SOURCE_TILEDIMAGES)
167 if subdirA_image_relative_url or subdirB_image_relative_url: 149 tiled_images_B = RenderedPicturesComparisons.get_multilevel(
168 if subdirA_image_relative_url == subdirB_image_relative_url: 150 dictB_results, skp_name, gm_json.JSONKEY_SOURCE_TILEDIMAGES)
169 result_type = results.KEY__RESULT_TYPE__SUCCEEDED 151 # TODO(epoger): Report an error if we find tiles for A but not B?
170 elif not subdirA_image_relative_url: 152 if tiled_images_A and tiled_images_B:
171 result_type = results.KEY__RESULT_TYPE__NOCOMPARISON 153 # TODO(epoger): Report an error if we find a different number of tiles
172 elif not subdirB_image_relative_url: 154 # for A and B?
173 result_type = results.KEY__RESULT_TYPE__NOCOMPARISON 155 num_tiles = len(tiled_images_A)
174 else: 156 for tile_num in range(num_tiles):
175 result_type = results.KEY__RESULT_TYPE__FAILED 157 imagepairs_for_this_skp.append(self._create_image_pair(
158 test=skp_name,
159 config='%s-%d' % (gm_json.JSONKEY_SOURCE_TILEDIMAGES, tile_num),
160 image_dict_A=tiled_images_A[tile_num],
161 image_dict_B=tiled_images_B[tile_num]))
176 162
177 extra_columns_dict = { 163 for imagepair in imagepairs_for_this_skp:
178 results.KEY__EXTRACOLUMN__RESULT_TYPE: result_type, 164 if imagepair:
179 results.KEY__EXTRACOLUMN__BUILDER: builder, 165 all_image_pairs.add_image_pair(imagepair)
180 results.KEY__EXTRACOLUMN__TEST: image_name, 166 result_type = imagepair.extra_columns_dict\
181 # TODO(epoger): Right now, the client UI crashes if it receives 167 [results.KEY__EXTRACOLUMN__RESULT_TYPE]
182 # results that do not include a 'config' column. 168 if result_type != results.KEY__RESULT_TYPE__SUCCEEDED:
183 # Until we fix that, keep the client happy. 169 failing_image_pairs.add_image_pair(imagepair)
184 results.KEY__EXTRACOLUMN__CONFIG: 'TODO',
185 }
186
187 try:
188 image_pair = imagepair.ImagePair(
189 image_diff_db=self._image_diff_db,
190 base_url=self._image_base_url,
191 imageA_relative_url=subdirA_image_relative_url,
192 imageB_relative_url=subdirB_image_relative_url,
193 extra_columns=extra_columns_dict)
194 all_image_pairs.add_image_pair(image_pair)
195 if result_type != results.KEY__RESULT_TYPE__SUCCEEDED:
196 failing_image_pairs.add_image_pair(image_pair)
197 except (KeyError, TypeError):
198 logging.exception(
199 'got exception while creating ImagePair for image_name '
200 '"%s", builder "%s"' % (image_name, builder))
201 170
202 self._results = { 171 self._results = {
203 results.KEY__HEADER__RESULTS_ALL: all_image_pairs.as_dict(), 172 results.KEY__HEADER__RESULTS_ALL: all_image_pairs.as_dict(),
204 results.KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict(), 173 results.KEY__HEADER__RESULTS_FAILURES: failing_image_pairs.as_dict(),
205 } 174 }
206 175
176 def _validate_dict_version(self, result_dict):
177 """Raises Exception if the dict is not the type/version we know how to read.
178
179 Args:
180 result_dict: dictionary holding output of render_pictures
181 """
182 expected_header_type = 'ChecksummedImages'
183 expected_header_revision = 1
184
185 header = result_dict[gm_json.JSONKEY_HEADER]
186 header_type = header[gm_json.JSONKEY_HEADER_TYPE]
187 if header_type != expected_header_type:
188 raise Exception('expected header_type "%s", but got "%s"' % (
189 expected_header_type, header_type))
190 header_revision = header[gm_json.JSONKEY_HEADER_REVISION]
191 if header_revision != expected_header_revision:
192 raise Exception('expected header_revision %d, but got %d' % (
193 expected_header_revision, header_revision))
194
195 def _create_image_pair(self, test, config, image_dict_A, image_dict_B):
196 """Creates an ImagePair object for this pair of images.
197
198 Args:
199 test: string; name of the test
200 config: string; name of the config
201 image_dict_A: dict with JSONKEY_IMAGE_* keys, or None if no image
202 image_dict_B: dict with JSONKEY_IMAGE_* keys, or None if no image
203
204 Returns:
205 An ImagePair object, or None if both image_dict_A and image_dict_B are
206 None.
207 """
208 if (not image_dict_A) and (not image_dict_B):
209 return None
210
211 def _checksum_and_relative_url(dic):
212 if dic:
213 return ((dic[gm_json.JSONKEY_IMAGE_CHECKSUMALGORITHM],
214 dic[gm_json.JSONKEY_IMAGE_CHECKSUMVALUE]),
215 dic[gm_json.JSONKEY_IMAGE_FILEPATH])
216 else:
217 return None, None
218
219 imageA_checksum, imageA_relative_url = _checksum_and_relative_url(
220 image_dict_A)
221 imageB_checksum, imageB_relative_url = _checksum_and_relative_url(
222 image_dict_B)
223
224 if not imageA_checksum:
225 result_type = results.KEY__RESULT_TYPE__NOCOMPARISON
226 elif not imageB_checksum:
227 result_type = results.KEY__RESULT_TYPE__NOCOMPARISON
228 elif imageA_checksum == imageB_checksum:
229 result_type = results.KEY__RESULT_TYPE__SUCCEEDED
230 else:
231 result_type = results.KEY__RESULT_TYPE__FAILED
232
233 extra_columns_dict = {
234 results.KEY__EXTRACOLUMN__CONFIG: config,
235 results.KEY__EXTRACOLUMN__RESULT_TYPE: result_type,
236 results.KEY__EXTRACOLUMN__TEST: test,
237 # TODO(epoger): Right now, the client UI crashes if it receives
238 # results that do not include this column.
239 # Until we fix that, keep the client happy.
240 results.KEY__EXTRACOLUMN__BUILDER: 'TODO',
241 }
242
243 try:
244 return imagepair.ImagePair(
245 image_diff_db=self._image_diff_db,
246 base_url=self._image_base_url,
247 imageA_relative_url=imageA_relative_url,
248 imageB_relative_url=imageB_relative_url,
249 extra_columns=extra_columns_dict)
250 except (KeyError, TypeError):
251 logging.exception(
252 'got exception while creating ImagePair for'
253 ' test="%s", config="%s", urlPair=("%s","%s")' % (
254 test, config, imageA_relative_url, imageB_relative_url))
255 return None
256
207 257
208 # TODO(epoger): Add main() so this can be called by vm_run_skia_try.sh 258 # TODO(epoger): Add main() so this can be called by vm_run_skia_try.sh
OLDNEW
« no previous file with comments | « gm/rebaseline_server/compare_configs.py ('k') | gm/rebaseline_server/compare_to_expectations.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698