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

Side by Side Diff: scripts/slave/recipe_modules/auto_bisect/api.py

Issue 2247373002: Refactor stages 1, 2 and test_api overhaul. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@master
Patch Set: Addressing all early feedback. Created 4 years, 3 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 # Copyright 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """API for the bisect recipe module. 5 """API for the bisect recipe module.
6 6
7 This API is meant to enable the bisect recipe to bisect any chromium-supported 7 This API is meant to enable the bisect recipe to bisect any chromium-supported
8 platform for any test that can be run via buildbot, perf or otherwise. 8 platform for any test that can be run via buildbot, perf or otherwise.
9 """ 9 """
10 10
11 import os 11 import os
12 12
13 from recipe_engine import recipe_api 13 from recipe_engine import recipe_api
14 from . import bisector 14 from auto_bisect import bisector
15 from . import depot_config 15 from auto_bisect import depot_config
16 from . import revision_state 16 from auto_bisect import revision_state
17 from . import local_bisect 17 from auto_bisect import local_bisect
18 18
19 BISECT_CONFIG_FILE = 'tools/auto_bisect/bisect.cfg' 19 BISECT_CONFIG_FILE = 'tools/auto_bisect/bisect.cfg'
20 20
21 21
22 class AutoBisectApi(recipe_api.RecipeApi): 22 class AutoBisectApi(recipe_api.RecipeApi):
23 """A module for bisect specific functions.""" 23 """A module for bisect specific functions."""
24 24
25 # Number of seconds to wait between polls for test results. 25 # Number of seconds to wait between polls for test results.
26 POLLING_INTERVAL = 60 26 POLLING_INTERVAL = 60
27 # GS bucket to use for communicating results and job state between bisector 27 # GS bucket to use for communicating results and job state between bisector
(...skipping 22 matching lines...) Expand all
50 # Keep track of working directory (which contains the checkout). 50 # Keep track of working directory (which contains the checkout).
51 # None means "default value". 51 # None means "default value".
52 self._working_dir = None 52 self._working_dir = None
53 53
54 @property 54 @property
55 def working_dir(self): 55 def working_dir(self):
56 if not self._working_dir: 56 if not self._working_dir:
57 self._working_dir = self.m.chromium_checkout.get_checkout_dir({}) 57 self._working_dir = self.m.chromium_checkout.get_checkout_dir({})
58 return self._working_dir or self.m.path['slave_build'] 58 return self._working_dir or self.m.path['slave_build']
59 59
60 def perform_bisect(self, **flags):
61 return local_bisect.perform_bisect(self, **flags)
62
63 def create_bisector(self, bisect_config_dict, dummy_mode=False, **flags): 60 def create_bisector(self, bisect_config_dict, dummy_mode=False, **flags):
64 """Passes the api and the config dictionary to the Bisector constructor. 61 """Passes the api and the config dictionary to the Bisector constructor.
65 62
66 For details about the keys in the bisect config dictionary go to: 63 For details about the keys in the bisect config dictionary go to:
67 http://chromium.org/developers/speed-infra/perf-try-bots-bisect-bots/config 64 http://chromium.org/developers/speed-infra/perf-try-bots-bisect-bots/config
68 65
69 Args: 66 Args:
70 bisect_config_dict (dict): Contains the configuration for the bisect job. 67 bisect_config_dict (dict): Contains the configuration for the bisect job.
71 dummy_mode (bool): In dummy mode we prevent the bisector for expanding 68 dummy_mode (bool): In dummy mode we prevent the bisector for expanding
72 the revision range at construction, to avoid the need for lots of step 69 the revision range at construction, to avoid the need for lots of step
(...skipping 24 matching lines...) Expand all
97 depot_config.add_addition_depot_into(depot_info) # pragma: no cover 94 depot_config.add_addition_depot_into(depot_info) # pragma: no cover
98 95
99 def set_deploy_script(self, path): # pragma: no cover 96 def set_deploy_script(self, path): # pragma: no cover
100 """Sets apk deployment script path for android-chrome.""" 97 """Sets apk deployment script path for android-chrome."""
101 self.full_deploy_script = path 98 self.full_deploy_script = path
102 99
103 def set_svn_repo(self, svn_repo_url): # pragma: no cover 100 def set_svn_repo(self, svn_repo_url): # pragma: no cover
104 """Sets SVN repo url for triggering build jobs.""" 101 """Sets SVN repo url for triggering build jobs."""
105 self.svn_repo_url = svn_repo_url 102 self.svn_repo_url = svn_repo_url
106 103
107 def gsutil_file_exists(self, path): 104 def gsutil_file_exists(self, path, **kwargs):
108 """Returns True if a file exists at the given GS path.""" 105 """Returns True if a file exists at the given GS path."""
109 try: 106 try:
110 self.m.gsutil(['ls', path]) 107 self.m.gsutil( ['ls', path], **kwargs)
111 except self.m.step.StepFailure: # pragma: no cover 108 except self.m.step.StepFailure:
112 # A step failure here simply means that the file does not exist, and 109 # A step failure here simply means that the file does not exist, and
113 # should not be treated as an error. 110 # should not be treated as an error.
114 self.m.step.active_result.presentation.status = self.m.step.SUCCESS 111 self.m.step.active_result.presentation.status = self.m.step.SUCCESS
115 return False 112 return False
116 return True 113 return True
117 114
118 def query_revision_info(self, revision): 115 def query_revision_info(self, revision):
119 """Gathers information on a particular revision. 116 """Gathers information on a particular revision.
120 117
121 Args: 118 Args:
122 revision (str): A git commit hash. 119 revision (str): A git commit hash.
123 120
124 Returns: 121 Returns:
125 A dict with the keys "author", "email", "date", "subject" and "body", 122 A dict with the keys "author", "email", "date", "subject" and "body",
126 as output by fetch_revision_info.py. 123 as output by fetch_revision_info.py.
127 """ 124 """
128 step_name = 'Reading culprit cl information.' 125 step_name = 'Reading culprit cl information.'
129 # Use gitiles to get commit information. 126 # Use gitiles to get commit information.
130 if revision.depot_name == 'android-chrome': # pragma: no cover 127 if revision.depot_name == 'android-chrome': # pragma: no cover
131 commit_url = depot_config.DEPOT_DEPS_NAME[revision.depot_name]['url'] 128 commit_url = depot_config.DEPOT_DEPS_NAME[revision.depot_name]['url']
132 return self._commit_info(revision.commit_hash, commit_url, step_name) 129 return self._commit_info(revision.commit_hash, commit_url, step_name)
133 result = self.m.python( 130 result = self.m.python(
134 step_name, 131 step_name,
135 self.resource('fetch_revision_info.py'), 132 self.resource('fetch_revision_info.py'),
136 [revision.commit_hash, revision.depot_name], 133 [revision.commit_hash, revision.depot_name],
137 stdout=self.m.json.output()) 134 stdout=self.m.json.output(),
135 step_test_data=lambda: self._test_data['cl_info'][revision.commit_hash],
136 )
138 return result.stdout 137 return result.stdout
139 138
140 def _commit_info(self, commit_hash, url, step_name=None): # pragma: no cover 139 def _commit_info(self, commit_hash, url, step_name=None): # pragma: no cover
141 """Fetches information about a given commit. 140 """Fetches information about a given commit.
142 141
143 Args: 142 Args:
144 commit_hash (str): A git commit hash. 143 commit_hash (str): A git commit hash.
145 url (str): The URL of a repository, e.g. 144 url (str): The URL of a repository, e.g.
146 "https://chromium.googlesource.com/chromium/src". 145 "https://chromium.googlesource.com/chromium/src".
147 step_name (str): Optional step name. 146 step_name (str): Optional step name.
(...skipping 14 matching lines...) Expand all
162 'email': commit_info['author']['email'], 161 'email': commit_info['author']['email'],
163 'subject': subject, 162 'subject': subject,
164 'body': body, 163 'body': body,
165 'date': commit_info['committer']['time'], 164 'date': commit_info['committer']['time'],
166 } 165 }
167 return None 166 return None
168 except self.m.step.StepFailure: # pragma: no cover 167 except self.m.step.StepFailure: # pragma: no cover
169 self.surface_result('BAD_REV') 168 self.surface_result('BAD_REV')
170 raise 169 raise
171 170
172 def run_bisect_script(self, **kwargs):
173 """Executes src/tools/run-perf-bisect-regression.py to perform bisection."""
174 self.m.python(
175 'Preparing for Bisection',
176 script=self.m.path['checkout'].join(
177 'tools', 'prepare-bisect-perf-regression.py'),
178 args=['-w', self.m.path['cache'].join('bisect')])
179 args = []
180
181 kwargs['allow_subannotations'] = True
182
183 # TODO(prasadv): Remove this once bisect runs are no longer running
184 # against revisions from February 2016 or earlier.
185 if self.internal_bisect: # pragma: no cover
186 kwargs['env'] = {'CHROMIUM_OUTPUT_DIR': self.m.chromium.output_dir}
187
188 if kwargs.get('extra_src'):
189 args = args + ['--extra_src', kwargs.pop('extra_src')]
190 if kwargs.get('path_to_config'):
191 args = args + ['--path_to_config', kwargs.pop('path_to_config')]
192 if self.m.chromium.c.TARGET_PLATFORM != 'android':
193 goma_dir = self.m.goma.ensure_goma()
194 args += ['--path_to_goma', goma_dir]
195 args += [
196 '--build-properties',
197 self.m.json.dumps(dict(self.m.properties.legacy())),
198 ]
199 self.m.chromium.runtest(
200 self.m.path['checkout'].join('tools', 'run-bisect-perf-regression.py'),
201 ['-w', self.m.path['cache'].join('bisect')] + args,
202 name='Running Bisection',
203 xvfb=True, **kwargs)
204
205 def run_local_test_run(self, test_config_params, 171 def run_local_test_run(self, test_config_params,
206 skip_download=False): # pragma: no cover 172 skip_download=False, **kwargs):
207 """Starts a test run on the same machine. 173 """Starts a test run on the same machine.
208 174
209 This is for the merged director/tester flow. 175 This is for the merged director/tester flow.
210 """ 176 """
211 if self.m.platform.is_win: 177 if self.m.platform.is_win:
212 self.m.chromium.taskkill() 178 self.m.chromium.taskkill()
213 179
214 if skip_download: 180 if skip_download: # pragma: no cover
215 update_step = None 181 update_step = None
216 else: 182 else:
217 update_step = self._SyncRevisionToTest(test_config_params) 183 update_step = self._SyncRevisionToTest(test_config_params)
218 self.start_test_run_for_bisect(update_step, self.bot_db, 184 return self.start_test_run_for_bisect(
219 test_config_params, run_locally=True, 185 update_step, self.bot_db, test_config_params,
220 skip_download=skip_download) 186 skip_download=skip_download, **kwargs)
221 187
222 def ensure_checkout(self, *args, **kwargs): 188 def ensure_checkout(self, *args, **kwargs):
223 if self.working_dir: 189 if self.working_dir:
224 kwargs.setdefault('cwd', self.working_dir) 190 kwargs.setdefault('cwd', self.working_dir)
225 191
226 return self.m.bot_update.ensure_checkout(*args, **kwargs) 192 return self.m.bot_update.ensure_checkout(*args, **kwargs)
227 193
228 def _SyncRevisionToTest(self, test_config_params): # pragma: no cover 194 def _SyncRevisionToTest(self, test_config_params):
229 if not self.internal_bisect: 195 if not self.internal_bisect:
230 return self.ensure_checkout( 196 return self.ensure_checkout(
231 root_solution_revision=test_config_params['revision']) 197 root_solution_revision=test_config_params['revision'])
232 else: 198 else: # pragma: no cover
233 return self._SyncRevisionsForAndroidChrome( 199 return self._SyncRevisionsForAndroidChrome(
234 test_config_params['revision_ladder']) 200 test_config_params['revision_ladder'])
235 201
236 def _SyncRevisionsForAndroidChrome(self, revision_ladder): # pragma: no cover 202 def _SyncRevisionsForAndroidChrome(self, revision_ladder): # pragma: no cover
237 """Syncs android-chrome and chromium repos to particular revision.""" 203 """Syncs android-chrome and chromium repos to particular revision."""
238 revisions = [] 204 revisions = []
239 for d, r in revision_ladder.iteritems(): 205 for d, r in revision_ladder.iteritems():
240 revisions.append('%s@%s' % (depot_config.DEPOT_DEPS_NAME[d]['src'], r)) 206 revisions.append('%s@%s' % (depot_config.DEPOT_DEPS_NAME[d]['src'], r))
241 params = ['sync', '--verbose', '--nohooks', '--force', 207 params = ['sync', '--verbose', '--nohooks', '--force',
242 '--delete_unversioned_trees'] 208 '--delete_unversioned_trees']
243 for revision in revisions: 209 for revision in revisions:
244 params.extend(['--revision', revision]) 210 params.extend(['--revision', revision])
245 self.m.gclient('sync %s' % '-'.join(revisions), params) 211 self.m.gclient('sync %s' % '-'.join(revisions), params)
246 return None 212 return None
247 213
248 def start_test_run_for_bisect(self, update_step, bot_db, 214 def start_test_run_for_bisect(self, update_step, bot_db, test_config_params,
249 test_config_params, run_locally=False, 215 skip_download=False, **kwargs):
250 skip_download=False):
251 mastername = self.m.properties.get('mastername') 216 mastername = self.m.properties.get('mastername')
252 buildername = self.m.properties.get('buildername') 217 buildername = self.m.properties.get('buildername')
253 bot_config = bot_db.get_bot_config(mastername, buildername) 218 bot_config = bot_db.get_bot_config(mastername, buildername)
254 build_archive_url = test_config_params['parent_build_archive_url'] 219 build_archive_url = test_config_params['parent_build_archive_url']
255 if not run_locally:
256 self.m.bisect_tester.upload_job_url()
257 if not skip_download: 220 if not skip_download:
258 if self.m.chromium.c.TARGET_PLATFORM == 'android': 221 if self.m.chromium.c.TARGET_PLATFORM == 'android':
259 # The best way to ensure the old build directory is not used is to 222 # The best way to ensure the old build directory is not used is to
260 # remove it. 223 # remove it.
261 build_dir = self.m.chromium.c.build_dir.join( 224 build_dir = self.m.chromium.c.build_dir.join(
262 self.m.chromium.c.build_config_fs) 225 self.m.chromium.c.build_config_fs)
263 self.m.file.rmtree('build directory', build_dir) 226 self.m.file.rmtree('build directory', build_dir)
264 227
265 # The way android builders on tryserver.chromium.perf are archived is 228 # The way android builders on tryserver.chromium.perf are archived is
266 # different from builders on chromium.perf. In order to support both 229 # different from builders on chromium.perf. In order to support both
(...skipping 22 matching lines...) Expand all
289 shutil.move(sys.argv[1], sys.argv[2]) 252 shutil.move(sys.argv[1], sys.argv[2])
290 """, 253 """,
291 args=[zip_dir, build_dir]) 254 args=[zip_dir, build_dir])
292 else: 255 else:
293 self.m.chromium_tests.download_and_unzip_build( 256 self.m.chromium_tests.download_and_unzip_build(
294 mastername, buildername, update_step, bot_db, 257 mastername, buildername, update_step, bot_db,
295 build_archive_url=build_archive_url, 258 build_archive_url=build_archive_url,
296 build_revision=test_config_params['parent_got_revision'], 259 build_revision=test_config_params['parent_got_revision'],
297 override_bot_type='tester') 260 override_bot_type='tester')
298 261
299 tests = [self.m.chromium_tests.steps.BisectTest(test_config_params)] 262 tests = [
263 self.m.chromium_tests.steps.BisectTest(
264 test_config_params, **kwargs)]
300 265
301 if not tests: # pragma: no cover 266 if not tests: # pragma: no cover
302 return 267 return
303 self.m.chromium_swarming.configure_swarming( 268 self.m.chromium_swarming.configure_swarming(
304 'chromium', precommit=False, mastername=mastername) 269 'chromium', precommit=False, mastername=mastername)
305 test_runner = self.m.chromium_tests.create_test_runner(self.m, tests) 270 test_runner = self.m.chromium_tests.create_test_runner(self.m, tests)
306 271
307 bot_config_object = self.m.chromium_tests.create_bot_config_object( 272 bot_config_object = self.m.chromium_tests.create_bot_config_object(
308 mastername, buildername) 273 mastername, buildername)
309 with self.m.chromium_tests.wrap_chromium_tests(bot_config_object, tests): 274 with self.m.chromium_tests.wrap_chromium_tests(bot_config_object, tests):
310 if self.m.chromium.c.TARGET_PLATFORM == 'android' and not skip_download: 275 if self.m.chromium.c.TARGET_PLATFORM == 'android' and not skip_download:
311 deploy_apks = [] 276 deploy_apks = []
312 deploy_args = [] 277 deploy_args = []
313 if self.internal_bisect: # pragma: no cover 278 if self.internal_bisect: # pragma: no cover
314 deploy_args = list(bot_config['deploy_args']) 279 deploy_args = list(bot_config['deploy_args'])
315 deploy_apks = bot_config.get('deploy_apks') 280 deploy_apks = bot_config.get('deploy_apks')
316 if deploy_apks: 281 if deploy_apks:
317 deploy_args.extend([ 282 deploy_args.extend([
318 '--apks', 283 '--apks',
319 ','.join(str(self.m.chromium_android.apk_path(apk)) 284 ','.join(str(self.m.chromium_android.apk_path(apk))
320 for apk in deploy_apks)]) 285 for apk in deploy_apks)])
321 else: 286 else:
322 if bot_config.get('webview'): 287 if bot_config.get('webview'):
323 deploy_apks = ['SystemWebView.apk', 'SystemWebViewShell.apk'] 288 deploy_apks = ['SystemWebView.apk', 'SystemWebViewShell.apk']
324 else: 289 else:
325 deploy_apks = ['ChromePublic.apk'] 290 deploy_apks = ['ChromePublic.apk']
326 self.deploy_apk_on_device( 291 self.deploy_apk_on_device(
327 self.full_deploy_script, deploy_apks, deploy_args) 292 self.full_deploy_script, deploy_apks, deploy_args)
328 test_runner() 293 test_runner()
294 return tests[0].run_results
329 295
330 def deploy_apk_on_device(self, deploy_script, deploy_apks, deploy_args): 296 def deploy_apk_on_device(self, deploy_script, deploy_apks, deploy_args):
331 """Installs apk on the android device.""" 297 """Installs apk on the android device."""
332 if deploy_script: # pragma: no cover 298 if deploy_script: # pragma: no cover
333 self.full_deploy_on_device(deploy_script, deploy_args) 299 self.full_deploy_on_device(deploy_script, deploy_args)
334 else: 300 else:
335 for apk in deploy_apks: 301 for apk in deploy_apks:
336 self.m.chromium_android.adb_install_apk(apk) 302 self.m.chromium_android.adb_install_apk(apk)
337 303
338 def full_deploy_on_device(self, deploy_script, args=None): # pragma: no cover 304 def full_deploy_on_device(self, deploy_script, args=None): # pragma: no cover
(...skipping 29 matching lines...) Expand all
368 api: The recipe api object. 334 api: The recipe api object.
369 update_step: Extra update_step to, used for some job types. 335 update_step: Extra update_step to, used for some job types.
370 bot_db: A BotConfigAndTestDB object, used for some job types. 336 bot_db: A BotConfigAndTestDB object, used for some job types.
371 kwargs: Args to use only for legacy bisect. 337 kwargs: Args to use only for legacy bisect.
372 """ 338 """
373 flags = {} 339 flags = {}
374 if kwargs.get('do_not_nest_wait_for_revision'): 340 if kwargs.get('do_not_nest_wait_for_revision'):
375 flags['do_not_nest_wait_for_revision'] = kwargs.pop( 341 flags['do_not_nest_wait_for_revision'] = kwargs.pop(
376 'do_not_nest_wait_for_revision') 342 'do_not_nest_wait_for_revision')
377 if bot_db is None: # pragma: no cover 343 if bot_db is None: # pragma: no cover
378 self.bot_db = api.chromium_tests.create_bot_db_from_master_dict( 344 self.bot_db = api.chromium_tests.create_bot_db_from_master_dict('', None)
379 '', None, None)
380 else: 345 else:
381 self.bot_db = bot_db 346 self.bot_db = bot_db
382 347
383 context = {} 348 context = {}
384 if self.working_dir: 349 if self.working_dir:
385 context['cwd'] = self.working_dir 350 context['cwd'] = self.working_dir
386 351
387 with api.step.context(context): 352 with api.step.context(context):
388 affected_files = self.m.tryserver.get_files_affected_by_patch() 353 affected_files = self.m.tryserver.get_files_affected_by_patch()
389 # Skip device setup for internal bisect as it is taken care in 354 # Skip device setup for internal bisect as it is taken care in
390 # internal recipes. 355 # internal recipes.
391 if (api.chromium.c.TARGET_PLATFORM == 'android' and 356 if (api.chromium.c.TARGET_PLATFORM == 'android' and
392 not self.internal_bisect): 357 not self.internal_bisect):
393 api.chromium_android.common_tests_setup_steps( 358 api.chromium_android.common_tests_setup_steps(
394 perf_setup=True, remove_system_webview=True) 359 perf_setup=True, remove_system_webview=True)
395 api.chromium.runhooks() 360 api.chromium.runhooks()
396 try: 361 try:
397 # Run legacy bisect script if the patch contains bisect.cfg.
398 if BISECT_CONFIG_FILE in affected_files: 362 if BISECT_CONFIG_FILE in affected_files:
399 api.step('***LEGACY BISECT (deprecated)***', []) 363 api.step('***LEGACY BISECT HAS BEEN DEPRECATED***', [])
400 self.run_bisect_script(**kwargs) 364 api.step.active_result.presentation.status = api.step.FAILURE
365 return
401 elif api.properties.get('bisect_config'): 366 elif api.properties.get('bisect_config'):
402 # We can distinguish between a config for a full bisect vs a single 367 # We can distinguish between a config for a full bisect vs a single
403 # test by checking for the presence of the good_revision key. 368 # test by checking for the presence of the good_revision key.
404 if api.properties.get('bisect_config').get('good_revision'): 369 if api.properties.get('bisect_config').get('good_revision'):
405 api.step('***BISECT***', []) 370 api.step('***BISECT***', [])
406 local_bisect.perform_bisect(self, **flags) # pragma: no cover 371 local_bisect.perform_bisect(self, **flags)
407 else: 372 else:
408 api.step('***SINGLE TEST (deprecated)***', []) 373 api.step('***SINGLE TEST (deprecated)***', [])
409 self.start_test_run_for_bisect(update_step, self.bot_db, 374 self.start_test_run_for_bisect(update_step, self.bot_db,
410 api.properties) 375 api.properties)
411 else: 376 else:
412 api.step('***PERF TRYJOB***', []) 377 api.step('***PERF TRYJOB***', [])
413 self.m.perf_try.start_perf_try_job( 378 self.m.perf_try.start_perf_try_job(
414 api, affected_files, update_step, self.bot_db) 379 api, affected_files, update_step, self.bot_db)
415 finally: 380 finally:
416 if api.chromium.c.TARGET_PLATFORM == 'android': 381 if api.chromium.c.TARGET_PLATFORM == 'android':
417 if self.internal_bisect: # pragma: no cover 382 if self.internal_bisect: # pragma: no cover
418 api.chromium_android.init_and_sync( 383 api.chromium_android.init_and_sync(
419 gclient_config=api.chromium_android.c.internal_dir_name, 384 gclient_config=api.chromium_android.c.internal_dir_name,
420 use_bot_update=True) 385 use_bot_update=True)
421 else: 386 else:
422 self.ensure_checkout() 387 self.ensure_checkout()
423 api.chromium_android.common_tests_final_steps() 388 api.chromium_android.common_tests_final_steps()
389
390
391 def stat_compare(self, values_a, values_b, metric,
392 output_format='chartjson', **kwargs):
393 """Compares samples using catapult's statistics implementation.
394
395 Args:
396 values_a, values_b: lists of paths to the json files containing the values
397 produced by the test.
398 metric: the name of the metric as sent by dashboard.
399 output_format: 'chartjson', 'valueset' or 'buildbot'
400
401 Returns:
402 a dict containing 'result' which may be True, False or 'needMoreData', as
403 well as details about each sample ('debug_values', 'mean' and 'std_dev').
404
405 """
406 args = [','.join(map(str, values_a)),
407 ','.join(map(str, values_b)),
408 metric,
409 '--' + output_format]
410
411 script = self.m.path['catapult'].join(
412 'tracing', 'bin', 'compare_samples_cmdline')
413 return self.m.python(
414 'Compare samples',
415 script=script,
416 args=args,
417 stdout=self.m.json.output(),
418 **kwargs).stdout
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698