| OLD | NEW |
| 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 """Provides a JSON endpoint for CQ data as a series of Trace Viewer events. | 5 """Provides a JSON endpoint for CQ data as a series of Trace Viewer events. |
| 6 | 6 |
| 7 From the CQ data posted to the datastore, patch_timeline will construct a JSON | 7 From the CQ data posted to the datastore, patch_timeline will construct a JSON |
| 8 object that can be parsed by Trace Viewer to create a timeline view. | 8 object that can be parsed by Trace Viewer to create a timeline view. |
| 9 """ | 9 """ |
| 10 | 10 |
| (...skipping 25 matching lines...) Expand all Loading... |
| 36 | 36 |
| 37 def get_attempts(issue, patch): # pragma: no cover | 37 def get_attempts(issue, patch): # pragma: no cover |
| 38 """Given an issue and a patch, returns a list of attempts. | 38 """Given an issue and a patch, returns a list of attempts. |
| 39 | 39 |
| 40 Returns a list of attempts. Attempts are lists of records which fall within | 40 Returns a list of attempts. Attempts are lists of records which fall within |
| 41 the endpoints of patch_start and patch_stop actions, inclusive. | 41 the endpoints of patch_start and patch_stop actions, inclusive. |
| 42 """ | 42 """ |
| 43 query = Record.query().order(Record.timestamp).filter( | 43 query = Record.query().order(Record.timestamp).filter( |
| 44 Record.tags == TAG_ISSUE % issue, | 44 Record.tags == TAG_ISSUE % issue, |
| 45 Record.tags == TAG_PATCHSET % patch) | 45 Record.tags == TAG_PATCHSET % patch) |
| 46 attempts = [] | |
| 47 attempt = None | 46 attempt = None |
| 48 for record in query: | 47 for record in query: |
| 49 action = record.fields.get('action') | 48 action = record.fields.get('action') |
| 50 if attempt is None and action == 'patch_start': | 49 if attempt is None and action == 'patch_start': |
| 51 attempt = [record] | 50 attempt = [record] |
| 52 # Sometimes CQ sends multiple patch_start in a single attempt. These | 51 # Sometimes CQ sends multiple patch_start in a single attempt. These |
| 53 # are ignored (only the first patch_start is kept). | 52 # are ignored (only the first patch_start is kept). |
| 54 if attempt is not None and action != 'patch_start': | 53 if attempt is not None and action != 'patch_start': |
| 55 attempt.append(record) | 54 attempt.append(record) |
| 56 if action == 'patch_stop': | 55 if action == 'patch_stop': |
| 57 attempts.append(attempt) | 56 yield attempt |
| 58 attempt = None | 57 attempt = None |
| 59 if attempt != None: | 58 if attempt != None: |
| 60 attempts.append(attempt) | 59 yield attempt |
| 61 return attempts | |
| 62 | 60 |
| 63 | 61 |
| 64 def attempts_to_events(attempts): # pragma: no cover | 62 def attempts_to_events(attempts): # pragma: no cover |
| 65 """Given a list of attempts, returns a list of Trace Viewer events. | 63 """Given a list of attempts, returns a list of Trace Viewer events. |
| 66 | 64 |
| 67 Attempts are a list of CQ records which fall between patch_start and | 65 Attempts are a list of CQ records which fall between patch_start and |
| 68 patch_stop actions. Each record is converted to a Trace Viewer event | 66 patch_stop actions. Each record is converted to a Trace Viewer event |
| 69 of type 'B' or 'E', representing begin and end respectively. | 67 of type 'B' or 'E', representing begin and end respectively. |
| 70 | 68 |
| 71 Occasinally CQ runs jobs without first a record representing a trigger | 69 Occasinally CQ runs jobs without first a record representing a trigger |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 130 patch_ready_to_commit: single 'B' event representing start of commit attempt | 128 patch_ready_to_commit: single 'B' event representing start of commit attempt |
| 131 patch_committed: single 'E' event representing successful commit | 129 patch_committed: single 'E' event representing successful commit |
| 132 patch_failed: single 'E' event representing completed patch attempt | 130 patch_failed: single 'E' event representing completed patch attempt |
| 133 patch_stop: single 'E' event representing the end of the attempt. | 131 patch_stop: single 'E' event representing the end of the attempt. |
| 134 verifier_trigger: multiple 'B' events, one for each builder triggered. | 132 verifier_trigger: multiple 'B' events, one for each builder triggered. |
| 135 verifier_jobs_update: multiple 'E' events, one for each builder success | 133 verifier_jobs_update: multiple 'E' events, one for each builder success |
| 136 or failure. | 134 or failure. |
| 137 """ | 135 """ |
| 138 action = record.fields.get('action') | 136 action = record.fields.get('action') |
| 139 attempt_string = 'Attempt %d' % attempt_number | 137 attempt_string = 'Attempt %d' % attempt_number |
| 138 timestamp = record.fields.get('timestamp') |
| 140 if action == 'verifier_trigger': | 139 if action == 'verifier_trigger': |
| 141 timestamp = record.fields['timestamp'] | |
| 142 masters = record.fields.get('trybots', {}) | 140 masters = record.fields.get('trybots', {}) |
| 143 for master in masters: | 141 for master in masters: |
| 144 for builder in masters[master]: | 142 for builder in masters[master]: |
| 145 yield TraceViewerEvent(builder, master, 'B', timestamp, attempt_string, | 143 yield TraceViewerEvent(builder, master, 'B', timestamp, attempt_string, |
| 146 builder) | 144 builder, 'cq_build_running') |
| 147 elif action == 'verifier_jobs_update': | 145 elif action == 'verifier_jobs_update': |
| 148 job_states = record.fields.get('jobs', {}) | 146 job_states = record.fields.get('jobs', {}) |
| 149 # CQ splits jobs into lists based on their state. | 147 # CQ splits jobs into lists based on their state. |
| 150 for cq_job_state, jobs in job_states.iteritems(): | 148 for cq_job_state, jobs in job_states.iteritems(): |
| 151 # Jobs can be in many different states, JOB_STATE maps them to | 149 # Jobs can be in many different states, JOB_STATE maps them to |
| 152 # 'running' or not. | 150 # 'running' or not. |
| 153 job_state = JOB_STATE.get(cq_job_state) | 151 job_state = JOB_STATE.get(cq_job_state) |
| 154 if not job_state or job_state == 'running': | 152 if not job_state or job_state == 'running': |
| 155 continue | 153 continue |
| 156 for job_info in jobs: | 154 for job_info in jobs: |
| 157 master = job_info['master'] | 155 master = job_info['master'] |
| 158 builder = job_info['builder'] | 156 builder = job_info['builder'] |
| 159 timestamp = rietveld_timestamp(job_info['timestamp']) | 157 timestamp = rietveld_timestamp(job_info['timestamp']) |
| 158 cname = 'cq_build_' + job_state |
| 160 args = { | 159 args = { |
| 161 'build_url': job_info.get('url'), | 160 'build_url': job_info.get('url'), |
| 162 'job_state': job_state, | |
| 163 } | 161 } |
| 164 yield TraceViewerEvent(builder, master, 'E', timestamp, attempt_string, | 162 yield TraceViewerEvent(builder, master, 'E', timestamp, attempt_string, |
| 165 builder, args) | 163 builder, cname, args) |
| 166 elif action == 'patch_start': | 164 elif action == 'patch_start': |
| 167 yield TraceViewerEvent(attempt_string, 'Patch Progress', 'B', | 165 yield TraceViewerEvent(attempt_string, 'Patch Progress', 'B', |
| 168 record.fields['timestamp'], attempt_string, | 166 timestamp, attempt_string, |
| 169 'Patch Progress', {'job_state': 'attempt_running'}) | 167 'Patch Progress', 'cq_build_attempt_running') |
| 170 elif action == 'patch_ready_to_commit': | 168 elif action == 'patch_ready_to_commit': |
| 171 yield TraceViewerEvent('Patch Committing', 'Patch Progress', 'B', | 169 yield TraceViewerEvent('Patch Committing', 'Patch Progress', 'B', |
| 172 record.fields['timestamp'], attempt_string, | 170 timestamp, attempt_string, |
| 173 'Patch Progress', {'job_state': 'attempt_running'}) | 171 'Patch Progress', 'cq_build_attempt_running') |
| 174 elif action == 'patch_committed': | 172 elif action == 'patch_committed': |
| 175 yield TraceViewerEvent('Patch Committing', 'Patch Progress', 'E', | 173 yield TraceViewerEvent('Patch Committing', 'Patch Progress', 'E', |
| 176 record.fields['timestamp'], attempt_string, | 174 timestamp, attempt_string, |
| 177 'Patch Progress', {'job_state': 'attempt_passed'}) | 175 'Patch Progress', 'cq_build_attempt_passed') |
| 178 elif action == 'patch_stop': | 176 elif action == 'patch_stop': |
| 179 state = 'attempt_' | 177 cname = 'cq_build_attempt_' |
| 180 if 'successfully committed' in record.fields['message']: | 178 if 'successfully committed' in record.fields['message']: |
| 181 state += 'passed' | 179 cname += 'passed' |
| 182 else: | 180 else: |
| 183 state += 'failed' | 181 cname += 'failed' |
| 184 yield TraceViewerEvent(attempt_string, 'Patch Progress', 'E', | 182 yield TraceViewerEvent(attempt_string, 'Patch Progress', 'E', |
| 185 record.fields['timestamp'], attempt_string, | 183 timestamp, attempt_string, 'Patch Progress', |
| 186 'Patch Progress', { | 184 cname, {'action': action}) |
| 187 'job_state': state, | |
| 188 'action': action, | |
| 189 }) | |
| 190 | 185 |
| 191 | 186 |
| 192 class TraceViewerEvent(): # pragma: no cover | 187 class TraceViewerEvent(): # pragma: no cover |
| 193 """A class used to create JSON objects corresponding to an event. | 188 """A class used to create JSON objects corresponding to an event. |
| 194 | 189 |
| 195 Trace Viewer requires a specific set of fields, described below: | 190 Trace Viewer requires a specific set of fields, described below: |
| 196 | 191 |
| 197 name: the name of the event, displayed as a label on the interval | 192 name: the name of the event, displayed as a label on the interval |
| 198 cat: category of the event, used with the search functionality | 193 cat: category of the event, used with the search functionality |
| 199 ph: type of event. for CQ data, it will be 'B' or 'E' for begin or end | 194 ph: type of event. for CQ data, it will be 'B' or 'E' for begin or end |
| 200 ts: timestamp of event | 195 ts: timestamp of event |
| 201 pid: process id, used for grouping threads | 196 pid: process id, used for grouping threads |
| 202 tid: thread id, displayed to the left of all intervals with the same thread | 197 tid: thread id, displayed to the left of all intervals with the same thread |
| 203 """ | 198 """ |
| 204 def __init__(self, name, cat, ph, ts, pid, tid, args=None): | 199 def __init__(self, name, cat, ph, ts, pid, tid, cname, args=None): |
| 205 self.name = name | 200 self.name = name |
| 206 self.cat = cat | 201 self.cat = cat |
| 207 self.ph = ph | 202 self.ph = ph |
| 208 self.ts = ts | 203 self.ts = ts |
| 209 self.pid = pid | 204 self.pid = pid |
| 210 self.tid = tid | 205 self.tid = tid |
| 206 self.cname = cname |
| 211 self.args = args or {} | 207 self.args = args or {} |
| 212 | 208 |
| 213 def to_dict(self): | 209 def to_dict(self): |
| 214 return { | 210 return { |
| 215 'name': self.name, | 211 'name': self.name, |
| 216 'cat': self.cat, | 212 'cat': self.cat, |
| 217 'ph': self.ph, | 213 'ph': self.ph, |
| 218 'ts': int(self.ts * 1000000), | 214 'ts': int(self.ts * 1000000), |
| 219 'pid': self.pid, | 215 'pid': self.pid, |
| 220 'tid': self.tid, | 216 'tid': self.tid, |
| 217 'cname': self.cname, |
| 221 'args': self.args | 218 'args': self.args |
| 222 } | 219 } |
| 223 | 220 |
| 224 def builder_key(self): | 221 def builder_key(self): |
| 225 """Returns an identifier for the build of the form master/builder.""" | 222 """Returns an identifier for the build of the form master/builder.""" |
| 226 return self.cat + '/' + self.name | 223 return self.cat + '/' + self.name |
| 227 | 224 |
| 228 | 225 |
| 229 def rietveld_timestamp(timestamp_string): # pragma: no cover | 226 def rietveld_timestamp(timestamp_string): # pragma: no cover |
| 230 """Converts a Rietveld timestamp into a unix timestamp.""" | 227 """Converts a Rietveld timestamp into a unix timestamp.""" |
| 231 try: | 228 try: |
| 232 return to_unix_timestamp( | 229 return to_unix_timestamp( |
| 233 datetime.strptime(timestamp_string, RIETVELD_TIMESTAMP_FORMAT)) | 230 datetime.strptime(timestamp_string, RIETVELD_TIMESTAMP_FORMAT)) |
| 234 except ValueError: | 231 except ValueError: |
| 235 return None | 232 return None |
| OLD | NEW |