| OLD | NEW |
| 1 # Copyright 2017 The Chromium Authors. All rights reserved. | 1 # Copyright 2017 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 """URL endpoint containing server-side functionality for pinpoint jobs.""" | 5 """URL endpoint containing server-side functionality for pinpoint jobs.""" |
| 6 | 6 |
| 7 import json | 7 import json |
| 8 | 8 |
| 9 from google.appengine.api import users | 9 from google.appengine.api import users |
| 10 from google.appengine.ext import ndb | 10 from google.appengine.ext import ndb |
| 11 | 11 |
| 12 from dashboard import start_try_job | 12 from dashboard import start_try_job |
| 13 from dashboard.common import namespaced_stored_object | 13 from dashboard.common import namespaced_stored_object |
| 14 from dashboard.common import request_handler | 14 from dashboard.common import request_handler |
| 15 from dashboard.common import utils | 15 from dashboard.common import utils |
| 16 from dashboard.services import crrev_service | 16 from dashboard.services import crrev_service |
| 17 from dashboard.services import pinpoint_service | 17 from dashboard.services import pinpoint_service |
| 18 | 18 |
| 19 _BOTS_TO_DIMENSIONS = 'bot_dimensions_map' | 19 _BOTS_TO_DIMENSIONS = 'bot_dimensions_map' |
| 20 _PINPOINT_REPOSITORIES = 'repositories' | 20 _PINPOINT_REPOSITORIES = 'repositories' |
| 21 _ISOLATE_TARGETS = [ |
| 22 'angle_perftests', 'cc_perftests', 'gpu_perftests', |
| 23 'load_library_perf_tests', 'media_perftests', 'net_perftests', |
| 24 'performance_browser_tests', 'telemetry_perf_tests', |
| 25 'telemetry_perf_webview_tests', 'tracing_perftests'] |
| 21 | 26 |
| 22 | 27 |
| 23 class InvalidParamsError(Exception): | 28 class InvalidParamsError(Exception): |
| 24 pass | 29 pass |
| 25 | 30 |
| 26 | 31 |
| 27 class PinpointNewPrefillRequestHandler(request_handler.RequestHandler): | 32 class PinpointNewPrefillRequestHandler(request_handler.RequestHandler): |
| 28 def post(self): | 33 def post(self): |
| 29 story_filter = start_try_job.GuessStoryFilter(self.request.get('test_path')) | 34 story_filter = start_try_job.GuessStoryFilter(self.request.get('test_path')) |
| 30 self.response.write(json.dumps({'story_filter': story_filter})) | 35 self.response.write(json.dumps({'story_filter': story_filter})) |
| 31 | 36 |
| 32 | 37 |
| 33 class PinpointNewRequestHandler(request_handler.RequestHandler): | 38 class PinpointNewBisectRequestHandler(request_handler.RequestHandler): |
| 34 def post(self): | 39 def post(self): |
| 35 job_params = dict( | 40 job_params = dict( |
| 36 (a, self.request.get(a)) for a in self.request.arguments()) | 41 (a, self.request.get(a)) for a in self.request.arguments()) |
| 37 | 42 |
| 38 try: | 43 try: |
| 39 pinpoint_params = PinpointParamsFromBisectParams(job_params) | 44 pinpoint_params = PinpointParamsFromBisectParams(job_params) |
| 40 except InvalidParamsError as e: | 45 except InvalidParamsError as e: |
| 41 self.response.write(json.dumps({'error': e.message})) | 46 self.response.write(json.dumps({'error': e.message})) |
| 42 return | 47 return |
| 43 | 48 |
| 44 self.response.write(json.dumps(pinpoint_service.NewJob(pinpoint_params))) | 49 self.response.write(json.dumps(pinpoint_service.NewJob(pinpoint_params))) |
| 45 | 50 |
| 46 | 51 |
| 52 class PinpointNewPerfTryRequestHandler(request_handler.RequestHandler): |
| 53 def post(self): |
| 54 job_params = dict( |
| 55 (a, self.request.get(a)) for a in self.request.arguments()) |
| 56 |
| 57 try: |
| 58 pinpoint_params = PinpointParamsFromPerfTryParams(job_params) |
| 59 except InvalidParamsError as e: |
| 60 self.response.write(json.dumps({'error': e.message})) |
| 61 return |
| 62 |
| 63 self.response.write(json.dumps(pinpoint_service.NewJob(pinpoint_params))) |
| 64 |
| 65 |
| 47 def ParseMetricParts(test_path_parts): | 66 def ParseMetricParts(test_path_parts): |
| 48 metric_parts = test_path_parts[3:] | 67 metric_parts = test_path_parts[3:] |
| 49 | 68 |
| 50 # Normal test path structure, ie. M/B/S/foo/bar.html | 69 # Normal test path structure, ie. M/B/S/foo/bar.html |
| 51 if len(metric_parts) == 2: | 70 if len(metric_parts) == 2: |
| 52 return '', metric_parts[0], metric_parts[1] | 71 return '', metric_parts[0], metric_parts[1] |
| 53 | 72 |
| 54 # 3 part structure, so there's a TIR label in there. | 73 # 3 part structure, so there's a TIR label in there. |
| 55 # ie. M/B/S/timeToFirstMeaningfulPaint_avg/load_tools/load_tools_weather | 74 # ie. M/B/S/timeToFirstMeaningfulPaint_avg/load_tools/load_tools_weather |
| 56 if len(metric_parts) == 3: | 75 if len(metric_parts) == 3: |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 88 def ParseTIRLabelChartNameAndTraceName(test_path_parts): | 107 def ParseTIRLabelChartNameAndTraceName(test_path_parts): |
| 89 """Returns tir_label, chart_name, trace_name from a test path.""" | 108 """Returns tir_label, chart_name, trace_name from a test path.""" |
| 90 test = ndb.Key('TestMetadata', '/'.join(test_path_parts)).get() | 109 test = ndb.Key('TestMetadata', '/'.join(test_path_parts)).get() |
| 91 | 110 |
| 92 tir_label, chart_name, trace_name = ParseMetricParts(test_path_parts) | 111 tir_label, chart_name, trace_name = ParseMetricParts(test_path_parts) |
| 93 if trace_name and test.unescaped_story_name: | 112 if trace_name and test.unescaped_story_name: |
| 94 trace_name = test.unescaped_story_name | 113 trace_name = test.unescaped_story_name |
| 95 return tir_label, chart_name, trace_name | 114 return tir_label, chart_name, trace_name |
| 96 | 115 |
| 97 | 116 |
| 117 def _BotDimensionsFromBotName(bot_name): |
| 118 bots_to_dimensions = namespaced_stored_object.Get(_BOTS_TO_DIMENSIONS) |
| 119 dimensions = bots_to_dimensions.get(bot_name) |
| 120 if not dimensions: |
| 121 raise InvalidParamsError('No dimensions for bot %s defined.' % bot_name) |
| 122 return dimensions |
| 123 |
| 124 |
| 125 def PinpointParamsFromPerfTryParams(params): |
| 126 """Takes parameters from Dashboard's pinpoint-perf-job-dialog and returns |
| 127 a dict with parameters for a new Pinpoint job. |
| 128 |
| 129 Args: |
| 130 params: A dict in the following format: |
| 131 { |
| 132 'test_path': Test path for the metric being bisected. |
| 133 'start_commit': Git hash or commit position of earlier revision. |
| 134 'end_commit': Git hash or commit position of later revision. |
| 135 'start_repository': Repository for earlier revision. |
| 136 'end_repository': Repository for later revision. |
| 137 'extra_telemetry_args': Extra args for telemetry. |
| 138 } |
| 139 |
| 140 Returns: |
| 141 A dict of params for passing to Pinpoint to start a job, or a dict with an |
| 142 'error' field. |
| 143 """ |
| 144 if not utils.IsValidSheriffUser(): |
| 145 user = users.get_current_user() |
| 146 raise InvalidParamsError('User "%s" not authorized.' % user) |
| 147 |
| 148 # Pinpoint takes swarming dimensions, so we need to map bot name to those. |
| 149 test_path = params['test_path'] |
| 150 test_path_parts = test_path.split('/') |
| 151 bot_name = test_path_parts[1] |
| 152 suite = test_path_parts[2] |
| 153 |
| 154 dimensions = _BotDimensionsFromBotName(bot_name) |
| 155 |
| 156 # Pinpoint also requires you specify which isolate target to run the |
| 157 # test, so we derive that from the suite name. Eventually, this would |
| 158 # ideally be stored in a SparesDiagnostic but for now we can guess. |
| 159 target = 'telemetry_perf_tests' |
| 160 if suite in _ISOLATE_TARGETS: |
| 161 raise InvalidParamsError('Only telemetry is supported at the moment.') |
| 162 elif 'webview' in bot_name: |
| 163 target = 'telemetry_perf_webview_tests' |
| 164 |
| 165 start_repository = params['start_repository'] |
| 166 end_repository = params['end_repository'] |
| 167 start_commit = params['start_commit'] |
| 168 end_commit = params['end_commit'] |
| 169 |
| 170 start_git_hash = ResolveToGitHash(start_commit, start_repository) |
| 171 end_git_hash = ResolveToGitHash(end_commit, end_repository) |
| 172 |
| 173 supported_repositories = namespaced_stored_object.Get(_PINPOINT_REPOSITORIES) |
| 174 |
| 175 # Bail if it's not a supported repository to bisect on |
| 176 if not start_repository in supported_repositories: |
| 177 raise InvalidParamsError('Invalid repository: %s' % start_repository) |
| 178 if not end_repository in supported_repositories: |
| 179 raise InvalidParamsError('Invalid repository: %s' % end_repository) |
| 180 |
| 181 # Pinpoint only supports chromium at the moment, so just throw up a |
| 182 # different error for now. |
| 183 if start_repository != 'chromium' or end_repository != 'chromium': |
| 184 raise InvalidParamsError('Only chromium perf try jobs supported currently.') |
| 185 |
| 186 extra_telemetry_args = params['extra_telemetry_args'] |
| 187 |
| 188 email = users.get_current_user().email() |
| 189 job_name = 'Job on [%s/%s] for [%s]' % (bot_name, suite, email) |
| 190 |
| 191 browser = start_try_job.GuessBrowserName(bot_name) |
| 192 |
| 193 return { |
| 194 'configuration': bot_name, |
| 195 'browser': browser, |
| 196 'benchmark': suite, |
| 197 'trace': '', |
| 198 'chart': '', |
| 199 'tir_label': '', |
| 200 'story': '', |
| 201 'start_repository': start_repository, |
| 202 'end_repository': end_repository, |
| 203 'start_git_hash': start_git_hash, |
| 204 'end_git_hash': end_git_hash, |
| 205 'extra_telemetry_args': json.dumps(extra_telemetry_args), |
| 206 'bug_id': '', |
| 207 'auto_explore': '0', |
| 208 'target': target, |
| 209 'dimensions': json.dumps(dimensions), |
| 210 'email': email, |
| 211 'name': job_name |
| 212 } |
| 213 |
| 214 |
| 98 def PinpointParamsFromBisectParams(params): | 215 def PinpointParamsFromBisectParams(params): |
| 99 """Takes parameters from Dashboard's pinpoint-job-dialog and returns | 216 """Takes parameters from Dashboard's pinpoint-job-dialog and returns |
| 100 a dict with parameters for a new Pinpoint job. | 217 a dict with parameters for a new Pinpoint job. |
| 101 | 218 |
| 102 Args: | 219 Args: |
| 103 params: A dict in the following format: | 220 params: A dict in the following format: |
| 104 { | 221 { |
| 105 'test_path': Test path for the metric being bisected. | 222 'test_path': Test path for the metric being bisected. |
| 106 'start_git_hash': Git hash of earlier revision. | 223 'start_git_hash': Git hash of earlier revision. |
| 107 'end_git_hash': Git hash of later revision. | 224 'end_git_hash': Git hash of later revision. |
| 108 'start_repository': Repository for earlier revision. | 225 'start_repository': Repository for earlier revision. |
| 109 'end_repository': Repository for later revision. | 226 'end_repository': Repository for later revision. |
| 110 'bug_id': Associated bug. | 227 'bug_id': Associated bug. |
| 111 } | 228 } |
| 112 | 229 |
| 113 Returns: | 230 Returns: |
| 114 A dict of params for passing to Pinpoint to start a job, or a dict with an | 231 A dict of params for passing to Pinpoint to start a job, or a dict with an |
| 115 'error' field. | 232 'error' field. |
| 116 """ | 233 """ |
| 117 if not utils.IsValidSheriffUser(): | 234 if not utils.IsValidSheriffUser(): |
| 118 user = users.get_current_user() | 235 user = users.get_current_user() |
| 119 raise InvalidParamsError('User "%s" not authorized.' % user) | 236 raise InvalidParamsError('User "%s" not authorized.' % user) |
| 120 | 237 |
| 121 bots_to_dimensions = namespaced_stored_object.Get(_BOTS_TO_DIMENSIONS) | |
| 122 | |
| 123 # Pinpoint takes swarming dimensions, so we need to map bot name to those. | 238 # Pinpoint takes swarming dimensions, so we need to map bot name to those. |
| 124 test_path = params['test_path'] | 239 test_path = params['test_path'] |
| 125 test_path_parts = test_path.split('/') | 240 test_path_parts = test_path.split('/') |
| 126 bot_name = test_path_parts[1] | 241 bot_name = test_path_parts[1] |
| 127 suite = test_path_parts[2] | 242 suite = test_path_parts[2] |
| 128 story_filter = params['story_filter'] | 243 story_filter = params['story_filter'] |
| 129 | 244 |
| 130 # If functional bisects are speciied, Pinpoint expects these parameters to be | 245 # If functional bisects are speciied, Pinpoint expects these parameters to be |
| 131 # empty. | 246 # empty. |
| 132 bisect_mode = params['bisect_mode'] | 247 bisect_mode = params['bisect_mode'] |
| 133 if bisect_mode != 'performance' and bisect_mode != 'functional': | 248 if bisect_mode != 'performance' and bisect_mode != 'functional': |
| 134 raise InvalidParamsError('Invalid bisect mode %s specified.' % bisect_mode) | 249 raise InvalidParamsError('Invalid bisect mode %s specified.' % bisect_mode) |
| 135 | 250 |
| 136 tir_label = '' | 251 tir_label = '' |
| 137 chart_name = '' | 252 chart_name = '' |
| 138 trace_name = '' | 253 trace_name = '' |
| 139 if bisect_mode == 'performance': | 254 if bisect_mode == 'performance': |
| 140 tir_label, chart_name, trace_name = ParseTIRLabelChartNameAndTraceName( | 255 tir_label, chart_name, trace_name = ParseTIRLabelChartNameAndTraceName( |
| 141 test_path_parts) | 256 test_path_parts) |
| 142 | 257 |
| 143 dimensions = bots_to_dimensions.get(bot_name) | 258 dimensions = _BotDimensionsFromBotName(bot_name) |
| 144 if not dimensions: | |
| 145 raise InvalidParamsError('No dimensions for bot %s defined.' % bot_name) | |
| 146 | 259 |
| 147 # Pinpoint also requires you specify which isolate target to run the | 260 # Pinpoint also requires you specify which isolate target to run the |
| 148 # test, so we derive that from the suite name. Eventually, this would | 261 # test, so we derive that from the suite name. Eventually, this would |
| 149 # ideally be stored in a SparesDiagnostic but for now we can guess. | 262 # ideally be stored in a SparesDiagnostic but for now we can guess. |
| 150 isolate_targets = [ | |
| 151 'angle_perftests', 'cc_perftests', 'gpu_perftests', | |
| 152 'load_library_perf_tests', 'media_perftests', 'net_perftests', | |
| 153 'performance_browser_tests', 'telemetry_perf_tests', | |
| 154 'telemetry_perf_webview_tests', 'tracing_perftests'] | |
| 155 | |
| 156 target = 'telemetry_perf_tests' | 263 target = 'telemetry_perf_tests' |
| 157 if suite in isolate_targets: | 264 if suite in _ISOLATE_TARGETS: |
| 158 target = suite | 265 target = suite |
| 159 elif 'webview' in bot_name: | 266 elif 'webview' in bot_name: |
| 160 target = 'telemetry_perf_webview_tests' | 267 target = 'telemetry_perf_webview_tests' |
| 161 | 268 |
| 162 start_repository = params['start_repository'] | 269 start_repository = params['start_repository'] |
| 163 end_repository = params['end_repository'] | 270 end_repository = params['end_repository'] |
| 164 start_commit = params['start_commit'] | 271 start_commit = params['start_commit'] |
| 165 end_commit = params['end_commit'] | 272 end_commit = params['end_commit'] |
| 166 | 273 |
| 167 start_git_hash = ResolveToGitHash(start_commit, start_repository) | 274 start_git_hash = ResolveToGitHash(start_commit, start_repository) |
| (...skipping 29 matching lines...) Expand all Loading... |
| 197 'end_repository': end_repository, | 304 'end_repository': end_repository, |
| 198 'start_git_hash': start_git_hash, | 305 'start_git_hash': start_git_hash, |
| 199 'end_git_hash': end_git_hash, | 306 'end_git_hash': end_git_hash, |
| 200 'bug_id': params['bug_id'], | 307 'bug_id': params['bug_id'], |
| 201 'auto_explore': '1', | 308 'auto_explore': '1', |
| 202 'target': target, | 309 'target': target, |
| 203 'dimensions': json.dumps(dimensions), | 310 'dimensions': json.dumps(dimensions), |
| 204 'email': email, | 311 'email': email, |
| 205 'name': job_name | 312 'name': job_name |
| 206 } | 313 } |
| OLD | NEW |