Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 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 """This module is to handle manual triage of analysis result. | 5 """This module is to handle manual triage of analysis result. |
| 6 | 6 |
| 7 This handler will flag the analysis result as correct or incorrect. | 7 This handler will flag the analysis result as correct or incorrect. |
| 8 TODO: work on an automatic or semi-automatic way to triage analysis result. | 8 TODO: work on an automatic or semi-automatic way to triage analysis result. |
| 9 """ | 9 """ |
| 10 | 10 |
| 11 import calendar | 11 import calendar |
| 12 from datetime import datetime | 12 from datetime import datetime |
| 13 from datetime import timedelta | |
|
chanli
2016/06/13 20:21:04
nit: need an empty line below.
josiahk
2016/06/21 00:56:54
Done.
| |
| 14 from common.base_handler import BaseHandler | |
| 15 from common.base_handler import Permission | |
| 16 from model import result_status | |
| 17 from model.wf_analysis import WfAnalysis | |
| 18 from waterfall import buildbot | |
|
chanli
2016/06/13 20:21:04
Nit: move ln 14 - 18 under ln 22. And also leave 2
josiahk
2016/06/21 00:56:54
Done.
| |
| 13 | 19 |
| 14 from google.appengine.api import users | 20 from google.appengine.api import users |
| 15 from google.appengine.ext import ndb | 21 from google.appengine.ext import ndb |
|
stgao
2016/06/13 23:39:49
for imports, there is an order for different group
josiahk
2016/06/21 00:56:53
Thanks!
| |
| 16 | 22 |
| 17 from common.base_handler import BaseHandler | 23 |
| 18 from common.base_handler import Permission | 24 MATCHING_ANALYSIS_DAYS_AGO_START = 1 |
| 19 from model.wf_analysis import WfAnalysis | 25 MATCHING_ANALYSIS_DAYS_AGO_END = 1 |
| 20 from model import result_status | |
| 21 from waterfall import buildbot | |
| 22 | 26 |
| 23 | 27 |
| 24 @ndb.transactional | 28 def _GenPotentialCulpritTupleList(analysis): |
| 25 def _UpdateAnalysisResultStatus( | 29 """Generates a list of potential culprit tuples. |
| 26 master_name, builder_name, build_number, correct, user_name=None): | 30 |
| 27 analysis = WfAnalysis.Get(master_name, builder_name, build_number) | 31 Args: |
| 28 if not analysis or not analysis.completed: | 32 analysis: the analysis from which to generate a potenial culript tuple list. |
| 33 | |
| 34 Returns: | |
| 35 A list of cultprit tuples that each could look like: | |
| 36 | |
| 37 (step_name, test_name, revision) | |
| 38 | |
| 39 or could look like: | |
| 40 | |
| 41 (step_name, revision) | |
| 42 """ | |
| 43 potential_culprit_tuple_list = [] | |
| 44 | |
| 45 # Iterates through the failures, tests, and suspected_cls, appending potential | |
| 46 # (step_name, test_name, revision) and (step_name, revision) culprit tuples to | |
| 47 # the list. | |
| 48 for failure in analysis.result['failures']: | |
| 49 if failure.get('tests'): | |
| 50 for test in failure['tests']: | |
| 51 for suspected_cl in test.get('suspected_cls', []): | |
| 52 potential_culprit_tuple_list.append(( | |
| 53 failure['step_name'], | |
| 54 test['test_name'], | |
| 55 suspected_cl['revision'])) | |
| 56 else: | |
| 57 for suspected_cl in failure['suspected_cls']: | |
| 58 potential_culprit_tuple_list.append(( | |
| 59 failure['step_name'], | |
| 60 suspected_cl['revision'])) | |
| 61 | |
| 62 return potential_culprit_tuple_list | |
| 63 | |
| 64 | |
| 65 def _DoAnalysesMatch(analysis_1, analysis_2): | |
| 66 """Checks if two analyses match. | |
| 67 | |
| 68 Args: | |
| 69 analysis_1: The first analysis to compare | |
| 70 analysis_2: The second analysis to compare | |
| 71 | |
| 72 Returns: | |
| 73 True if the two analyses' sorted potential culprit lists match, otherwise | |
| 74 False. | |
| 75 """ | |
| 76 | |
| 77 # Get list of potential culprit tuples | |
| 78 potential_culprit_tuple_list_1 = _GenPotentialCulpritTupleList(analysis_1) | |
| 79 potential_culprit_tuple_list_2 = _GenPotentialCulpritTupleList(analysis_2) | |
| 80 | |
| 81 # Both analyses must have non-empty potential culprit lists | |
| 82 if not potential_culprit_tuple_list_1 or not potential_culprit_tuple_list_2: | |
| 29 return False | 83 return False |
| 30 | 84 |
| 85 # Both analyses must have matching potential culprit lists | |
| 86 if (sorted(potential_culprit_tuple_list_1) != | |
|
lijeffrey
2016/06/13 18:15:06
you can just do
return sorted(potential_culprit_t
josiahk
2016/06/21 00:56:54
Done. Thanks!
| |
| 87 sorted(potential_culprit_tuple_list_2)): | |
| 88 return False | |
| 89 | |
| 90 return True | |
| 91 | |
| 92 | |
| 93 def _AppendTriageHistoryRecord(analysis, correct, user_name): | |
| 94 """Appends a triage history record to the given analysis. | |
| 95 | |
| 96 Args: | |
| 97 analysis: The analysis to which to append the history record | |
|
lijeffrey
2016/06/13 18:15:06
. after sentences.
josiahk
2016/06/21 00:56:54
Done.
| |
| 98 correct: True if the history record should indicate a correct judgement, | |
| 99 otherwise False. | |
| 100 user_name: The user_name of the person to include in the triage record | |
| 101 """ | |
| 31 if correct: | 102 if correct: |
| 32 if analysis.suspected_cls: | 103 if analysis.suspected_cls: |
| 33 analysis.result_status = result_status.FOUND_CORRECT | 104 analysis.result_status = result_status.FOUND_CORRECT |
| 34 analysis.culprit_cls = analysis.suspected_cls | 105 analysis.culprit_cls = analysis.suspected_cls |
| 35 else: | 106 else: |
| 36 analysis.result_status = result_status.NOT_FOUND_CORRECT | 107 analysis.result_status = result_status.NOT_FOUND_CORRECT |
| 37 analysis.culprit_cls = None | 108 analysis.culprit_cls = None |
| 38 else: | 109 else: |
| 39 analysis.culprit_cls = None | 110 analysis.culprit_cls = None |
| 40 if analysis.suspected_cls: | 111 if analysis.suspected_cls: |
| 41 analysis.result_status = result_status.FOUND_INCORRECT | 112 analysis.result_status = result_status.FOUND_INCORRECT |
| 42 else: | 113 else: |
| 43 analysis.result_status = result_status.NOT_FOUND_INCORRECT | 114 analysis.result_status = result_status.NOT_FOUND_INCORRECT |
| 44 | 115 |
| 45 triage_record = { | 116 triage_record = { |
| 46 'triage_timestamp': calendar.timegm(datetime.utcnow().timetuple()), | 117 'triage_timestamp': calendar.timegm(datetime.utcnow().timetuple()), |
| 47 'user_name': user_name, | 118 'user_name': user_name, |
| 48 'result_status': analysis.result_status, | 119 'result_status': analysis.result_status, |
| 49 'version': analysis.version, | 120 'version': analysis.version, |
| 50 } | 121 } |
| 51 if not analysis.triage_history: | 122 if not analysis.triage_history: |
| 52 analysis.triage_history = [] | 123 analysis.triage_history = [] |
| 53 analysis.triage_history.append(triage_record) | 124 analysis.triage_history.append(triage_record) |
| 54 | 125 |
| 55 analysis.put() | 126 analysis.put() |
| 127 | |
| 128 | |
| 129 @ndb.transactional | |
| 130 def _UpdateAnalysisResultStatus( | |
| 131 master_name, builder_name, build_number, correct, user_name=None): | |
| 132 analysis = WfAnalysis.Get(master_name, builder_name, build_number) | |
| 133 if not analysis or not analysis.completed: | |
| 134 return False | |
| 135 | |
| 136 _AppendTriageHistoryRecord(analysis, correct, user_name) | |
| 137 | |
| 138 return True, analysis | |
| 139 | |
| 140 # TODO: Add @ndb.transactional? | |
|
lijeffrey
2016/06/13 18:15:06
do some research as to what ndb.transactional mean
| |
| 141 def _TriageDuplicateResults(original_analysis, correct, user_name=None): | |
| 142 # QUESTION: Instead of looking for analyses with start_dates relative to | |
|
lijeffrey
2016/06/13 18:15:06
when you find out the answer take this comment out
chanli
2016/06/13 20:21:04
I think your idea makes sense, but how relative wo
josiahk
2016/06/21 00:56:54
Thanks! I have taken the comment out. I also talke
| |
| 143 # today, should we be looking for analyses with start_dates relative to the | |
| 144 # date of the original analysis? | |
|
stgao
2016/06/13 23:39:49
Should it be relative to the wf_analysis.WfAnalysi
josiahk
2016/06/21 00:56:54
Yes! Thank you!
| |
| 145 | |
| 146 start_date = datetime.utcnow() - timedelta(MATCHING_ANALYSIS_DAYS_AGO_START) | |
| 147 start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0) | |
| 148 end_date = datetime.utcnow() - timedelta(MATCHING_ANALYSIS_DAYS_AGO_END - 1) | |
|
chanli
2016/06/13 20:21:03
Why didn't make MATCHING_ANALYSIS_DAYS_AGO_END = 0
josiahk
2016/06/21 00:56:53
Weird code removed. Underlying logic changed.
| |
| 149 end_date = end_date.replace(hour=0, minute=0, second=0, microsecond=0) | |
| 150 | |
| 151 analysis_results = WfAnalysis.query(ndb.AND( | |
| 152 WfAnalysis.build_start_time >= start_date, | |
| 153 WfAnalysis.build_start_time < end_date, | |
| 154 WfAnalysis.result_status == result_status.FOUND_UNTRIAGED | |
| 155 )).fetch() | |
| 156 | |
| 157 # Generate list of matching build analyses | |
| 158 matching_analyses = [a for a in analysis_results if | |
| 159 _DoAnalysesMatch(original_analysis, a) and | |
| 160 original_analysis.key is not a.key] | |
| 161 | |
| 162 for a in matching_analyses: | |
|
lijeffrey
2016/06/13 18:15:06
nit: use meaningful variable names
josiahk
2016/06/21 00:56:54
Done.
| |
| 163 _AppendTriageHistoryRecord(a, correct, user_name) | |
| 164 | |
| 165 # QUESTION: Should this function ever return anything but True? | |
|
lijeffrey
2016/06/13 18:15:06
see lines 133-134
josiahk
2016/06/21 00:56:54
Thanks!
| |
| 56 return True | 166 return True |
| 57 | 167 |
| 58 | 168 |
| 59 class TriageAnalysis(BaseHandler): | 169 class TriageAnalysis(BaseHandler): |
| 60 PERMISSION_LEVEL = Permission.CORP_USER | 170 PERMISSION_LEVEL = Permission.CORP_USER |
| 61 | 171 |
| 62 def HandleGet(self): # pragma: no cover | 172 def HandleGet(self): # pragma: no cover |
| 63 return self.HandlePost() | 173 return self.HandlePost() |
| 64 | 174 |
| 65 def HandlePost(self): | 175 def HandlePost(self): |
| 66 """Sets the manual triage result for the analysis. | 176 """Sets the manual triage result for the analysis. |
| 67 | 177 |
| 68 Mark the analysis result as correct/wrong/etc. | 178 Mark the analysis result as correct/wrong/etc. |
| 69 TODO: make it possible to set the real culprit CLs. | 179 TODO: make it possible to set the real culprit CLs. |
| 70 """ | 180 """ |
| 71 url = self.request.get('url').strip() | 181 url = self.request.get('url').strip() |
| 72 build_info = buildbot.ParseBuildUrl(url) | 182 build_info = buildbot.ParseBuildUrl(url) |
| 73 if not build_info: | 183 if not build_info: |
| 74 return {'data': {'success': False}} | 184 return {'data': {'success': False}} |
| 75 master_name, builder_name, build_number = build_info | 185 master_name, builder_name, build_number = build_info |
| 76 | 186 |
| 77 correct = self.request.get('correct').lower() == 'true' | 187 correct = self.request.get('correct').lower() == 'true' |
| 78 # As the permission level is CORP_USER, we could assume the current user | 188 # As the permission level is CORP_USER, we could assume the current user |
| 79 # already logged in. | 189 # already logged in. |
| 80 user_name = users.get_current_user().email().split('@')[0] | 190 user_name = users.get_current_user().email().split('@')[0] |
| 81 success = _UpdateAnalysisResultStatus( | 191 success_and_maybe_analysis = _UpdateAnalysisResultStatus( |
| 82 master_name, builder_name, build_number, correct, user_name) | 192 master_name, builder_name, build_number, correct, user_name) |
| 193 success = not not success_and_maybe_analysis and _TriageDuplicateResults( | |
|
lijeffrey
2016/06/13 18:15:06
not not shouldn't be necessary
chanli
2016/06/13 20:21:04
Based your current change, how could you display t
josiahk
2016/06/21 00:56:54
Done.
josiahk
2016/06/21 00:56:54
The original analysis link information will hopefu
| |
| 194 success_and_maybe_analysis[1], correct, user_name) | |
| 83 return {'data': {'success': success}} | 195 return {'data': {'success': success}} |
| OLD | NEW |