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

Side by Side Diff: appengine/findit/crash/findit.py

Issue 2414523002: [Findit] Reorganizing findit_for_*.py (Closed)
Patch Set: more debugging Created 4 years, 2 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
(Empty)
1 # Copyright 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import copy
6 import logging
7
8 from google.appengine.ext import ndb
9
10 from common import appengine_util
11 from common import chromium_deps
12 from common import constants
13 from common import time_util
14 from crash.crash_pipeline import CrashWrapperPipeline
15 from crash.crash_report import CrashReport
16 from crash.culprit import NullCulprit
17 from model import analysis_status
18 from model.crash.crash_config import CrashConfig
19 from model.crash.cracas_crash_analysis import CracasCrashAnalysis
20 from model.crash.fracas_crash_analysis import FracasCrashAnalysis
21
22
23 # TODO: this class depends on ndb stuff, and should therefore move to
24 # cr-culprit-finder/service/predator as part of the big reorganization.
25 # Also, this class should be renamed to "PredatorApp" (alas, "Azalea"
26 # was renamed to "Predator").
27 class Findit(object):
28 def __init__(self):
29 # TODO(wrengr): because self.client is volatile, we need some way
30 # of updating the Azelea instance whenever the config changes. How to
31 # do that cleanly?
32 self.azalea = None
33 self.stacktrace_parser = None
34
35 # This is a method rather than an attribute to ensure it can't be
36 # changed. And it's a classmethod rather than a property, since we can
37 # get it directly from the class itself, without needing to allocate
38 # an instance first.
39 @classmethod
40 def ClientID(cls):
41 """Reify the name of this class as a CrashClient id, for serialization."""
42 raise NotImplementedError()
43
44 @property
45 def client_id(self):
46 """Get the client id from the class of this object.
47
48 N.B., this property is static and should not be overridden."""
49 return self.__class__.ClientID()
50
51 # TODO(wrengr): can we remove the dependency on CrashConfig
52 # entirely? It'd be better to receive method calls whenever things
53 # change, so that we know the change happened (and what in particular
54 # changed) so that we can update our internal state as appropriate.
55 @property
56 def config(self):
57 """Get the current value of the client config.
58
59 N.B., this property is volatile and may change asynchronously."""
60 return CrashConfig.Get().GetClientConfig(self.client_id)
61
62 # TODO(wrengr): rename to CanonicalizePlatform or something like that.
63 def RenamePlatform(self, platform):
64 """Remap the platform to a different one, based on the config."""
65 # TODO(katesonia): Remove the default value after adding validity check to
66 # config.
67 return self.config.get('platform_rename', {}).get(platform, platform)
68
69 def CheckPolicy(self, crash_data):
70 """Check whether this client supports analyzing the given report.
71
72 Some clients only support analysis for crashes on certain platforms
73 or channels, etc. This method checks to see whether this client can
74 analyze the given crash.
75
76 Args:
77 crash_data (dict from JSON): ??
78
79 Returns:
80 If satisfied, we return the |crash_data| which may have had some
81 fields modified. Otherwise returns None.
82 """
83 return None
84
85 # TODO(wrengr): rename this to something like _NewAnalysis, since
86 # it only does the "allocation" and needs to/will be followed up with
87 # _InitializeAnalysis anyways.
88 def CreateAnalysis(self, crash_identifiers):
89 return None
90
91 def GetAnalysis(self, crash_identifiers):
92 """Return the CrashAnalysis for the |crash_identifiers|, if one exists.
93
94 Args:
95 crash_identifiers (??): ??
96
97 Returns:
98 If a CrashAnalysis ndb.Model already exists for the
99 |crash_identifiers|, then we return it. Otherwise, returns None.
100 """
101 return None
102
103 # TODO(wrengr): this should be a method on CrashAnalysis, not on Findit.
104 def _InitializeAnalysis(self, model, crash_data):
105 """(Re)Initialize a CrashAnalysis ndb.Model, but do not |put()| it yet.
106
107 This method is only ever called from _NeedsNewAnalysis which is only
108 ever called from ScheduleNewAnalysis. It is used for filling in the
109 fields of a CrashAnalysis ndb.Model for the first time (though it
110 can also be used to re-initialize a given CrashAnalysis). Subclasses
111 should extend (not override) this to (re)initialize any
112 client-specific fields they may have."""
113 # Get rid of any previous values there may have been.
114 model.Reset()
115
116 # Set the version.
117 # |handlers.crash.test.crash_handler_test.testAnalysisScheduled|
118 # provides and expects this field to be called 'chrome_version',
119 # whereas everyone else (e.g., in |crash.test.crash_pipeline_test|
120 # the tests |testAnalysisNeededIfNoAnalysisYet|,
121 # |testRunningAnalysisNoSuspectsFound|, |testRunningAnalysis|,
122 # |testAnalysisNeededIfLastOneFailed|, |testRunningAnalysisWithSuspectsCls|)
123 # expects it to be called 'crashed_version'. The latter is the
124 # better/more general name, so the former needs to be changed in
125 # order to get rid of this defaulting ugliness.
126 model.crashed_version = crash_data.get('crashed_version',
127 crash_data.get('chrome_version', None))
128
129 # Set (other) common properties.
130 model.stack_trace = crash_data['stack_trace']
131 model.signature = crash_data['signature']
132 model.platform = crash_data['platform']
133 # TODO(wrengr): assert that crash_data['client_id'] == self.client_id
134 model.client_id = self.client_id
135
136 # Set progress properties.
137 model.status = analysis_status.PENDING
138 model.requested_time = time_util.GetUTCNow()
139
140 @ndb.transactional
141 def _NeedsNewAnalysis(self, crash_data):
142 raise NotImplementedError()
143
144 def ScheduleNewAnalysis(self, crash_data, queue_name=constants.DEFAULT_QUEUE):
145 """Schedules an analysis."""
146 # Check policy and tune arguments if needed.
147 crash_data = self.CheckPolicy(crash_data)
148 if crash_data is None:
149 return False
150
151 # Detect the regression range, and decide if we actually need to
152 # run a new anlaysis or not.
153 if not self._NeedsNewAnalysis(crash_data):
154 return False
155
156 crash_identifiers = crash_data['crash_identifiers']
157 analysis_pipeline = CrashWrapperPipeline(self, crash_identifiers)
stgao 2016/10/19 18:20:53 The problem is here -- the `self`. Only JSON-seria
wrengr 2016/10/22 00:18:49 IIRC the previous version just passed the client_i
158 # Attribute defined outside __init__ - pylint: disable=W0201
159 analysis_pipeline.target = appengine_util.GetTargetNameForModule(
160 constants.CRASH_BACKEND[self.client_id])
161 analysis_pipeline.start(queue_name=queue_name)
162 logging.info('New %s analysis is scheduled for %s', self.client_id,
163 repr(crash_identifiers))
164 return True
165
166 # TODO(wrengr): does the parser actually need the version, signature,
167 # and platform? If not, then we should be able to just pass the string
168 # to be parsed (which would make a lot more sense than passing the
169 # whole model).
170 def ParseStacktrace(self, model):
171 """Parse a CrashAnalysis's |stack_trace| string into a Stacktrace object.
172
173 Args:
174 model (CrashAnalysis): The model containing the stack_trace string
175 to be parsed.
176
177 Returns:
178 On success, returns a Stacktrace object; on failure, returns None.
179 """
180 stacktrace = self.stacktrace_parser.Parse(model.stack_trace,
181 chromium_deps.GetChromeDependency(
182 model.crashed_version, model.platform),
183 model.signature)
184 if not stacktrace:
185 logging.warning('Failed to parse the stacktrace %s', model.stack_trace)
186 return None
187
188 return stacktrace
189
190 # TODO(wrengr): This is only called by |CrashAnalysisPipeline.run|;
191 # we should be able to adjust things so that we only need to take in
192 # |crash_identifiers|, or a CrashReport, rather than taking in the
193 # whole model.
194 # TODO(wrengr): as part of inverting crash_pipeline.py wrt
195 # findit.py, this method should probably be the one to create the
196 # |CrashWrapperPipeline| and |CrashAnalysisPipeline| therein, rather
197 # than the other way around.
198 def FindCulprit(self, model):
199 """Given a CrashAnalysis ndb.Model, return a Culprit."""
200 stacktrace = self.ParseStacktrace(model)
201 if stacktrace is None:
202 # TODO(wrengr): refactor things so we don't need the NullCulprit class.
203 return NullCulprit()
204
205 return self.azalea.FindCulprit(CrashReport(
206 crashed_version = model.crashed_version,
207 signature = model.signature,
208 platform = model.platform,
209 stacktrace = stacktrace,
210 regression_range = model.regression_range))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698