Browse files

Bug 792082 - Ignore the browser side of plugin-hang reports

  • Loading branch information...
1 parent 955b894 commit d4a65ec8a799d4afbb809136787b40ae850c5930 @twobraids twobraids committed Sep 18, 2012
View
19 docs/throttling.rst
@@ -27,22 +27,30 @@ throttleConditions
This option tells the collector how to route a given JSON/dump pair to
storage for further processing or deferred storage. This consists of a
-list of conditions in this form: (JsonFileKey?, ConditionFunction?,
+list of conditions in this form: (RawCrashKey?, ConditionFunction?,
Probability)
-* JsonFileKey?: a name of a field from the HTTP POST form. The
+* RawCrashKey?: a name of a field from the HTTP POST form. The
possibilities are: "StartupTime?", "Vendor", "InstallTime?",
"timestamp", "Add-ons", "BuildID", "SecondsSinceLastCrash?", "UserID",
"ProductName?", "URL", "Theme", "Version", "CrashTime?"
-* ConditionFunction?: a function returning a boolean, regular
- expression or a constant used to test the value for the
- JsonFileKey?.
+ Alternatively, the string "*" has special meaning when the
+ ConditionFunction? is a reference to a Python function.
+* ConditionFunction?: a function accepting a single string value and
+ returning a boolean; regular expression; or a constant used for an
+ equality test with the value for the RawCrashKey?.
+ Alternatively, If the RawCrashKey? is "*" and the function will be
+ passed the entire raw crash as a dict rather than just a single
+ value of one element of the raw crash.
* Probability: an integer between 0 and 100 inclusive. At 100, all
JSON files, for which the ConditionFunction? returns true, will be
saved in the database. At 0, no JSON files for which the
ConditionFunction? returns true will be saved to the database. At 25,
there is twenty-five percent probability that a matching JSON file
will be written to the database.
+ Alternatively, the value can be None. In that case, no probablity is
+ calculated and the throttler just returns the IGNORE value. The crash
+ is not stored and "Unsupported=1" is returned to the client.
There must be at least one entry in the throttleConditions list. The
example below shows the default case.
@@ -60,5 +68,6 @@ Keep the list short to avoid bogging down the collector.::
#("Add-ons", re.compile('inspector\@mozilla\.org\:1\..*'), 75), # queue 75% of crashes where the inspector addon is at 1.x
#("UserID", "d6d2b6b0-c9e0-4646-8627-0b1bdd4a92bb", 100), # queue all of this user's crashes
#("SecondsSinceLastCrash", lambda x: 300 >= int(x) >= 0, 100), # queue all crashes that happened within 5 minutes of another crash
+ ("*", lambda d: d["Product"] == "Flock" and d["Version"] == "3.0", None), # ignore Flock 3.0
(None, True, 10) # queue 10% of what's left
]
View
1 scripts/config/collectorconfig.py.dist
@@ -136,6 +136,7 @@ neverDiscard.fromStringConverter = cm.booleanConverter
throttleConditions = cm.Option()
throttleConditions.default = throttleConditions.default = [
+ ("*", lambda d: "HangID" in d and d.get("ProcessType", "browser") == "browser", None), # drop browser hangs
("Comments", lambda x: x, 100), # 100% of crashes with comments
("ReleaseChannel", lambda x: x in ("aurora", "beta", "esr"), 100),
("ReleaseChannel", lambda x: x.startswith('nightly'), 100),
View
26 socorro/collector/throttler.py
@@ -13,9 +13,10 @@
Compiled_Regular_Expression_Type = type(re.compile(''))
#--------------------------------------------------------------------------
-ACCEPT = 0
-DEFER = 1
-DISCARD = 2
+ACCEPT = 0 # save and process
+DEFER = 1 # save but don't process
+DISCARD = 2 # tell client to go away and not come back
+IGNORE = 3 # ignore this submission entirely
#==============================================================================
@@ -142,7 +143,10 @@ def apply_throttle_conditions(self, raw_crash):
for key, condition, percentage in self.processed_throttle_conditions:
throttle_match = False
try:
- throttle_match = condition(raw_crash[key])
+ if key == '*':
+ throttle_match = condition(raw_crash)
+ else:
+ throttle_match = condition(raw_crash[key])
except KeyError:
if key == None:
throttle_match = condition(None)
@@ -152,14 +156,24 @@ def apply_throttle_conditions(self, raw_crash):
except IndexError:
pass
if throttle_match: # we've got a condition match - apply percent
+ if percentage is None:
+ return None
random_real_percent = random.random() * 100.0
return random_real_percent > percentage
# nothing matched, reject
return True
#--------------------------------------------------------------------------
def throttle(self, raw_crash):
- if self.apply_throttle_conditions(raw_crash):
+ throttle_result = self.apply_throttle_conditions(raw_crash)
+ if throttle_result is None:
+ self.config.logger.debug(
+ "ignoring %s %s",
+ raw_crash.ProductName,
+ raw_crash.Version
+ )
+ return IGNORE
+ if throttle_result: # we're rejecting
#logger.debug('yes, throttle this one')
if (self.understands_refusal(raw_crash)
and not self.config.never_discard):
@@ -176,7 +190,7 @@ def throttle(self, raw_crash):
raw_crash.Version
)
return DEFER
- else:
+ else: # we're accepting
self.config.logger.debug(
"not throttled %s %s",
raw_crash.ProductName,
View
4 socorro/collector/wsgi_collector.py
@@ -7,7 +7,7 @@
from socorro.lib.ooid import createNewOoid
from socorro.lib.util import DotDict
-from socorro.collector.throttler import DISCARD
+from socorro.collector.throttler import DISCARD, IGNORE
from socorro.lib.datetimeutil import utc_now
@@ -60,6 +60,8 @@ def POST(self, *args):
raw_crash.legacy_processing = self.throttler.throttle(raw_crash)
if raw_crash.legacy_processing == DISCARD:
return "Discarded=1\n"
+ if raw_crash.legacy_processing == IGNORE:
+ return "Unsupported=1\n"
self.config.crash_storage.save_raw_crash(
raw_crash,
View
5 socorro/collector/wsgicollector.py
@@ -46,6 +46,11 @@ def POST(self, *args):
ooid = sooid.createNewOoid(currentTimestamp)
jsonDataDictionary.legacy_processing = \
self.legacyThrottler.throttle(jsonDataDictionary)
+
+ if jsonDataDictionary.legacy_processing == cstore.LegacyThrottler.IGNORE:
+ self.logger.info('%s ignored', ooid)
+ return "Unsupported=1\n"
+
self.logger.info('%s received', ooid)
result = crashStorage.save_raw(ooid,
jsonDataDictionary,
View
15 socorro/storage/crashstorage.py
@@ -81,6 +81,7 @@ def __init__(self, config):
ACCEPT = 0
DEFER = 1
DISCARD = 2
+ IGNORE = 3
#-----------------------------------------------------------------------------------------------------------------
@staticmethod
def regexpHandlerFactory(regexp):
@@ -143,7 +144,10 @@ def applyThrottleConditions (self, jsonData):
#logger.debug("throttle testing %s %s %d", key, condition, percentage)
throttleMatch = False
try:
- throttleMatch = condition(jsonData[key])
+ if key == '*':
+ throttleMatch = condition(jsonData)
+ else:
+ throttleMatch = condition(jsonData[key])
except KeyError:
if key == None:
throttleMatch = condition(None)
@@ -153,6 +157,8 @@ def applyThrottleConditions (self, jsonData):
except IndexError:
pass
if throttleMatch: #we've got a condition match - apply the throttle percentage
+ if percentage is None:
+ return None
randomRealPercent = random.random() * 100.0
#logger.debug("throttle: %f %f %s", randomRealPercent, percentage, randomRealPercent > percentage)
return randomRealPercent > percentage
@@ -179,7 +185,11 @@ def applyThrottleConditions (self, jsonData):
#-----------------------------------------------------------------------------------------------------------------
def throttle (self, jsonData):
- if self.applyThrottleConditions(jsonData):
+ result = self.applyThrottleConditions(jsonData)
+ if result is None:
+ logger.debug("ignoring %s %s", jsonData.ProductName, jsonData.Version)
+ return LegacyThrottler.IGNORE
+ if result:
#logger.debug('yes, throttle this one')
if self.understandsRefusal(jsonData) and not self.config.neverDiscard:
logger.debug("discarding %s %s", jsonData.ProductName, jsonData.Version)
@@ -229,6 +239,7 @@ def makeJsonDictFromForm (self, form, tm=tm):
DISCARDED = 2
ERROR = 3
RETRY = 4
+
#-----------------------------------------------------------------------------------------------------------------
def terminated (self, jsonData):
return False
View
274 socorro/unittest/collector/test_throttler.py
@@ -6,97 +6,189 @@
import mock
from socorro.lib.util import DotDict
-from socorro.collector.throttler import LegacyThrottler, ACCEPT, DEFER, DISCARD
+from socorro.collector.throttler import (
+ LegacyThrottler,
+ ACCEPT,
+ DEFER,
+ DISCARD,
+ IGNORE
+)
def testLegacyThrottler():
- config = DotDict()
- config.throttle_conditions = [ ('alpha', re.compile('ALPHA'), 100),
- ('beta', 'BETA', 100),
- ('gamma', lambda x: x == 'GAMMA', 100),
- ('delta', True, 100),
- (None, True, 0)
- ]
- config.minimal_version_for_understanding_refusal = { 'product1': '3.5', 'product2': '4.0' }
- config.never_discard = False
- config.logger = mock.Mock()
- thr = LegacyThrottler(config)
- expected = 5
- actual = len(thr.processed_throttle_conditions)
- assert expected == actual, "expected thr.preprocessThrottleConditions to have length %d, but got %d instead" % (expected, actual)
-
- raw_crash = DotDict({ 'ProductName':'product1',
- 'Version':'3.0',
- 'alpha':'ALPHA',
- })
- expected = False
- actual = thr.understands_refusal(raw_crash)
- assert expected == actual, "understand refusal expected %d, but got %d instead" % (expected, actual)
-
- raw_crash = DotDict({ 'ProductName':'product1',
- 'Version':'3.6',
- 'alpha':'ALPHA',
- })
- expected = True
- actual = thr.understands_refusal(raw_crash)
- assert expected == actual, "understand refusal expected %d, but got %d instead" % (expected, actual)
-
- expected = ACCEPT
- actual = thr.throttle(raw_crash)
- assert expected == actual, "regexp throttle expected %d, but got %d instead" % (expected, actual)
-
- raw_crash = DotDict({ 'ProductName':'product1',
- 'Version':'3.4',
- 'alpha':'not correct',
- })
- expected = DEFER
- actual = thr.throttle(raw_crash)
- assert expected == actual, "regexp throttle expected %d, but got %d instead" % (expected, actual)
-
- raw_crash = DotDict({ 'ProductName':'product1',
- 'Version':'3.6',
- 'alpha':'not correct',
- })
- expected = DISCARD
- actual = thr.throttle(raw_crash)
- assert expected == actual, "regexp throttle expected %d, but got %d instead" % (expected, actual)
-
- raw_crash = DotDict({ 'ProductName':'product1',
- 'Version':'3.6',
- 'beta':'BETA',
- })
- expected = ACCEPT
- actual = thr.throttle(raw_crash)
- assert expected == actual, "string equality throttle expected %d, but got %d instead" % (expected, actual)
-
- raw_crash = DotDict({ 'ProductName':'product1',
- 'Version':'3.6',
- 'beta':'not BETA',
- })
- expected = DISCARD
- actual = thr.throttle(raw_crash)
- assert expected == actual, "string equality throttle expected %d, but got %d instead" % (expected, actual)
-
- raw_crash = DotDict({ 'ProductName':'product1',
- 'Version':'3.6',
- 'gamma':'GAMMA',
- })
- expected = ACCEPT
- actual = thr.throttle(raw_crash)
- assert expected == actual, "string equality throttle expected %d, but got %d instead" % (expected, actual)
-
- raw_crash = DotDict({ 'ProductName':'product1',
- 'Version':'3.6',
- 'gamma':'not GAMMA',
- })
- expected = DISCARD
- actual = thr.throttle(raw_crash)
- assert expected == actual, "string equality throttle expected %d, but got %d instead" % (expected, actual)
-
- raw_crash = DotDict({ 'ProductName':'product1',
- 'Version':'3.6',
- 'delta':"value doesn't matter",
- })
- expected = ACCEPT
- actual = thr.throttle(raw_crash)
- assert expected == actual, "string equality throttle expected %d, but got %d instead" % (expected, actual)
+
+ # phase 1 tests
+
+ config = DotDict()
+ config.throttle_conditions = [ ('alpha', re.compile('ALPHA'), 100),
+ ('beta', 'BETA', 100),
+ ('gamma', lambda x: x == 'GAMMA', 100),
+ ('delta', True, 100),
+ (None, True, 0)
+ ]
+ config.minimal_version_for_understanding_refusal = {
+ 'product1': '3.5',
+ 'product2': '4.0'
+ }
+ config.never_discard = False
+ config.logger = mock.Mock()
+ thr = LegacyThrottler(config)
+ expected = 5
+ actual = len(thr.processed_throttle_conditions)
+ assert expected == actual, \
+ "expected thr.preprocessThrottleConditions to have length %d, but got " \
+ "%d instead" % (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.0',
+ 'alpha':'ALPHA',
+ })
+ expected = False
+ actual = thr.understands_refusal(raw_crash)
+ assert expected == actual, \
+ "understand refusal expected %d, but got %d instead" % (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'alpha':'ALPHA',
+ })
+ expected = True
+ actual = thr.understands_refusal(raw_crash)
+ assert expected == actual, \
+ "understand refusal expected %d, but got %d instead" % (expected, actual)
+
+ expected = ACCEPT
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "regexp throttle expected %d, but got %d instead" % (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.4',
+ 'alpha':'not correct',
+ })
+ expected = DEFER
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "regexp throttle expected %d, but got %d instead" % (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'alpha':'not correct',
+ })
+ expected = DISCARD
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "regexp throttle expected %d, but got %d instead" % (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'beta':'BETA',
+ })
+ expected = ACCEPT
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "string equality throttle expected %d, but got %d instead" % \
+ (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'beta':'not BETA',
+ })
+ expected = DISCARD
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "string equality throttle expected %d, but got %d instead" % \
+ (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'gamma':'GAMMA',
+ })
+ expected = ACCEPT
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "string equality throttle expected %d, but got %d instead" % \
+ (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'gamma':'not GAMMA',
+ })
+ expected = DISCARD
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "string equality throttle expected %d, but got %d instead" % \
+ (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'delta':"value doesn't matter",
+ })
+ expected = ACCEPT
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "string equality throttle expected %d, but got %d instead" % \
+ (expected, actual)
+
+ # phase 2 tests
+
+ config = DotDict()
+ config.throttle_conditions = [
+ ('*', lambda x: 'alpha' in x, None),
+ ('*', lambda x: x['beta'] == 'BETA', 100),
+ ]
+ config.minimal_version_for_understanding_refusal = {
+ 'product1': '3.5',
+ 'product2': '4.0'
+ }
+ config.never_discard = True
+ config.logger = mock.Mock()
+ thr = LegacyThrottler(config)
+ expected = 2
+ actual = len(thr.processed_throttle_conditions)
+ assert expected == actual, \
+ "expected thr.preprocessThrottleConditions to have length %d, but got " \
+ "%d instead" % (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'beta': 'ugh',
+ 'alpha':"value doesn't matter",
+ })
+ expected = IGNORE
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "IGNORE expected %d, but got %d instead" % \
+ (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'beta': 'ugh',
+ 'delta':"value doesn't matter",
+ })
+ expected = DEFER
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "DEFER expected %d, but got %d instead" % \
+ (expected, actual)
+
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'beta': 'BETA',
+ 'alpha':"value doesn't matter",
+ })
+ expected = IGNORE
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "IGNORE expected %d, but got %d instead" % \
+ (expected, actual)
+ raw_crash = DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'beta': 'BETA',
+ 'delta':"value doesn't matter",
+ })
+ expected = ACCEPT
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "ACCEPT expected %d, but got %d instead" % \
+ (expected, actual)
View
47 socorro/unittest/collector/test_wsgi_collector.py
@@ -10,7 +10,7 @@
from configman.dotdict import DotDict
from socorro.collector.wsgi_collector import Collector
-from socorro.collector.throttler import ACCEPT
+from socorro.collector.throttler import ACCEPT, IGNORE
class ObjectWithValue(object):
@@ -106,3 +106,48 @@ def test_POST(self):
'fake dump',
r[11:-1]
)
+
+ def test_POST_reject_browser_with_hangid(self):
+ config = self.get_standard_config()
+ c = Collector(config)
+ rawform = DotDict()
+ rawform.ProductName = 'FireFloozy'
+ rawform.Version = '99'
+ rawform.dump = DotDict({'value': 'fake dump', 'file': 'faked file'})
+ rawform.some_field = '23'
+ rawform.some_other_field = ObjectWithValue('XYZ')
+ rawform.HangID = 'xyz'
+ rawform.ProcessType = 'browser'
+
+ form = DotDict(rawform)
+ form.dump = rawform.dump.value
+
+ erc = DotDict()
+ erc.ProductName = 'FireFloozy'
+ erc.Version = '99'
+ erc.some_field = '23'
+ erc.some_other_field = 'XYZ'
+ erc.legacy_processing = ACCEPT
+ erc.timestamp = 3.0
+ erc.submitted_timestamp = '2012-05-04T15:10:00'
+ erc = dict(erc)
+
+ with mock.patch('socorro.collector.wsgi_collector.web') as mocked_web:
+ mocked_web.input.return_value = form
+ with mock.patch('socorro.collector.wsgi_collector.web.webapi') \
+ as mocked_webapi:
+ mocked_webapi.rawinput.return_value = rawform
+ with mock.patch('socorro.collector.wsgi_collector.utc_now') \
+ as mocked_utc_now:
+ mocked_utc_now.return_value = datetime(
+ 2012, 5, 4, 15, 10
+ )
+ with mock.patch('socorro.collector.wsgi_collector.time') \
+ as mocked_time:
+ mocked_time.time.return_value = 3.0
+ c.throttler.throttle.return_value = IGNORE
+ r = c.POST()
+ self.assertEqual(r, "Unsupported=1\n")
+ self.assertFalse(
+ c.crash_storage.save_raw_crash.call_count
+ )
View
63 socorro/unittest/storage/testCrashstorage.py
@@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import unittest
+import mock
import os
import sys
import re
@@ -132,6 +133,68 @@ def testLegacyThrottler():
expected = cstore.LegacyThrottler.ACCEPT
actual = thr.throttle(json1)
assert expected == actual, "string equality throttle expected %d, but got %d instead" % (expected, actual)
+ # phase 2 tests
+
+ config = util.DotDict()
+ config.throttleConditions = [
+ ('*', lambda x: 'alpha' in x, None),
+ ('*', lambda x: x['beta'] == 'BETA', 100),
+ ]
+ config.minimalVersionForUnderstandingRefusal = {
+ 'product1': '3.5',
+ 'product2': '4.0'
+ }
+ config.neverDiscard = True
+ config.logger = mock.Mock()
+ thr = cstore.LegacyThrottler(config)
+ expected = 2
+ actual = len(thr.processedThrottleConditions)
+ assert expected == actual, \
+ "expected thr.preprocessThrottleConditions to have length %d, but got " \
+ "%d instead" % (expected, actual)
+
+ raw_crash = util.DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'beta': 'ugh',
+ 'alpha':"value doesn't matter",
+ })
+ expected = cstore.LegacyThrottler.IGNORE
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "IGNORE expected %d, but got %d instead" % \
+ (expected, actual)
+
+ raw_crash = util.DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'beta': 'ugh',
+ 'delta':"value doesn't matter",
+ })
+ expected = cstore.LegacyThrottler.DEFER
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "DEFER expected %d, but got %d instead" % \
+ (expected, actual)
+
+ raw_crash = util.DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'beta': 'BETA',
+ 'alpha':"value doesn't matter",
+ })
+ expected = cstore.LegacyThrottler.IGNORE
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "IGNORE expected %d, but got %d instead" % \
+ (expected, actual)
+ raw_crash = util.DotDict({ 'ProductName':'product1',
+ 'Version':'3.6',
+ 'beta': 'BETA',
+ 'delta':"value doesn't matter",
+ })
+ expected = cstore.LegacyThrottler.ACCEPT
+ actual = thr.throttle(raw_crash)
+ assert expected == actual, \
+ "ACCEPT expected %d, but got %d instead" % \
+ (expected, actual)
def testCrashStorageSystem__init__():

0 comments on commit d4a65ec

Please sign in to comment.