Skip to content

fix(grouping): Store original in_app for idempotent regrouping #13540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion requirements-base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ redis>=2.10.3,<2.10.6
requests-oauthlib==0.3.3
requests[security]>=2.20.0,<2.21.0
selenium==3.141.0
semaphore>=0.4.36,<0.5.0
semaphore>=0.4.37,<0.5.0
sentry-sdk>=0.9.0
setproctitle>=1.1.7,<1.2.0
simplejson>=3.2.0,<3.9.0
Expand Down
3 changes: 2 additions & 1 deletion src/sentry/api/endpoints/event_grouping_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def get(self, request, project, event_id):
hashes = event.get_hashes()

try:
variants = event.get_grouping_variants(config_name)
variants = event.get_grouping_variants(force_config=config_name,
normalize_stacktraces=True)
except GroupingConfigNotFound:
raise ResourceDoesNotExist(detail='Unknown grouping config')

Expand Down
8 changes: 7 additions & 1 deletion src/sentry/grouping/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ def load_grouping_config(config_dict=None):
return CONFIGURATIONS[config_id](**config_dict)


def load_default_grouping_config():
return load_grouping_config(config_dict=None)


def get_fingerprinting_config_for_project(project):
from sentry.grouping.fingerprinting import FingerprintingRules, \
InvalidFingerprintingConfig
Expand Down Expand Up @@ -201,9 +205,11 @@ def get_grouping_variants_for_event(event, config=None):
'custom-fingerprint': CustomFingerprintVariant(fingerprint),
}

if config is None:
config = load_default_grouping_config()

# At this point we need to calculate the default event values. If the
# fingerprint is salted we will wrap it.
config = load_grouping_config(config)
components = _get_calculated_grouping_variants_for_event(event, config)
rv = {}

Expand Down
32 changes: 22 additions & 10 deletions src/sentry/grouping/enhancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
from parsimonious.exceptions import ParseError

from sentry import projectoptions
from sentry.stacktraces.functions import set_in_app
from sentry.stacktraces.platform import get_behavior_family_for_platform
from sentry.grouping.utils import get_rule_bool
from sentry.utils.compat import implements_to_string
from sentry.utils.glob import glob_match
from sentry.utils.safe import get_path


# Grammar is defined in EBNF syntax.
Expand Down Expand Up @@ -166,7 +168,7 @@ class Action(object):
def apply_modifications_to_frame(self, frames, idx):
pass

def update_frame_components_contributions(self, components, idx, rule=None):
def update_frame_components_contributions(self, components, frames, idx, rule=None):
pass

def modify_stack_state(self, state, rule):
Expand Down Expand Up @@ -207,23 +209,35 @@ def _slice_to_range(self, seq, idx):
return seq[idx + 1:]
return []

def _in_app_changed(self, frame, component):
orig_in_app = get_path(frame, 'data', 'orig_in_app')

if orig_in_app is not None:
if orig_in_app == -1:
orig_in_app = None
return orig_in_app != frame.get('in_app')
else:
return self.flag == component.contributes

def apply_modifications_to_frame(self, frames, idx):
# Grouping is not stored on the frame
if self.key == 'group':
return
for frame in self._slice_to_range(frames, idx):
if self.key == 'app':
frame['in_app'] = self.flag
set_in_app(frame, self.flag)

def update_frame_components_contributions(self, components, idx, rule=None):
def update_frame_components_contributions(self, components, frames, idx, rule=None):
rule_hint = 'grouping enhancement rule'
if rule:
rule_hint = '%s (%s)' % (
rule_hint,
rule.matcher_description,
)

for component in self._slice_to_range(components, idx):
sliced_components = self._slice_to_range(components, idx)
sliced_frames = self._slice_to_range(frames, idx)
for component, frame in izip(sliced_components, sliced_frames):
if self.key == 'group' and self.flag != component.contributes:
component.update(
contributes=self.flag,
Expand All @@ -232,9 +246,7 @@ def update_frame_components_contributions(self, components, idx, rule=None):
)
# The in app flag was set by `apply_modifications_to_frame`
# but we want to add a hint if there is none yet.
elif self.key == 'app' and \
self.flag == component.contributes and \
component.hint is None:
elif self.key == 'app' and self._in_app_changed(frame, component):
component.update(
hint='marked %s by %s' % (
self.flag and 'in-app' or 'out of app', rule_hint)
Expand Down Expand Up @@ -311,7 +323,7 @@ def update_frame_components_contributions(self, components, frames, platform):
actions = rule.get_matching_frame_actions(frame, platform)
for action in actions or ():
action.update_frame_components_contributions(
components, idx, rule=rule)
components, frames, idx, rule=rule)
action.modify_stack_state(stack_state, rule)

# Use the stack state to update frame contributions again
Expand Down Expand Up @@ -406,8 +418,8 @@ def __init__(self, matchers, actions):
@property
def matcher_description(self):
rv = ' '.join(x.description for x in self.matchers)
if any(x.range is not None for x in self.actions):
rv += ' - ranged'
for action in self.actions:
rv = '%s %s' % (rv, action)
return rv

def as_dict(self):
Expand Down
15 changes: 13 additions & 2 deletions src/sentry/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,19 @@ def get_hashes(self, force_config=None):
return filter(None, [
x.get_hash() for x in self.get_grouping_variants(force_config).values()])

def get_grouping_variants(self, force_config=None):
def get_grouping_variants(self, force_config=None, normalize_stacktraces=False):
"""
This is similar to `get_hashes` but will instead return the
grouping components for each variant in a dictionary.

If `normalize_stacktraces` is set to `True` then the event data will be
modified for `in_app` in addition to event variants being created. This
means that after calling that function the event data has been modified
in place.
"""
from sentry.grouping.api import get_grouping_variants_for_event
from sentry.grouping.api import get_grouping_variants_for_event, \
load_grouping_config
from sentry.stacktraces.processing import normalize_stacktraces_for_grouping

# Forcing configs has two separate modes. One is where just the
# config ID is given in which case it's merged with the stored or
Expand All @@ -194,6 +201,10 @@ def get_grouping_variants(self, force_config=None):
else:
config = self.data.get('grouping_config')

config = load_grouping_config(config)
if normalize_stacktraces:
normalize_stacktraces_for_grouping(self.data, config)

return get_grouping_variants_for_event(self, config)

def get_primary_hash(self):
Expand Down
13 changes: 12 additions & 1 deletion src/sentry/stacktraces/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re

from sentry.stacktraces.platform import get_behavior_family_for_platform
from sentry.utils.safe import setdefault_path


_windecl_hash = re.compile(r'^@?(.*?)@[0-9]+$')
Expand Down Expand Up @@ -109,7 +110,7 @@ def trim_function_name(function, platform, normalize_lambdas=True):
return function

# Chop off C++ trailers
while 1:
while True:
match = _cpp_trailer_re.search(function)
if match is None:
break
Expand Down Expand Up @@ -199,3 +200,13 @@ def get_function_name_for_frame(frame, platform=None):
rv = frame.get('function')
if rv:
return trim_function_name(rv, frame.get('platform') or platform)


def set_in_app(frame, value):
orig_in_app = frame.get('in_app')
if orig_in_app == value:
return

orig_in_app = int(orig_in_app) if orig_in_app is not None else -1
setdefault_path(frame, 'data', 'orig_in_app', value=orig_in_app)
frame['in_app'] = value
13 changes: 10 additions & 3 deletions src/sentry/stacktraces/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from sentry.utils.cache import cache
from sentry.utils.hashlib import hash_values
from sentry.utils.safe import get_path, safe_execute
from sentry.stacktraces.functions import trim_function_name
from sentry.stacktraces.functions import set_in_app, trim_function_name


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -218,12 +218,12 @@ def _normalize_in_app(stacktrace, platform=None, sdk_info=None):
for frame in stacktrace:
# If all frames are in_app, flip all of them. This is expected by the UI
if not has_system_frames:
frame['in_app'] = False
set_in_app(frame, False)

# Default to false in all cases where processors or grouping enhancers
# have not yet set in_app.
elif frame.get('in_app') is None:
frame['in_app'] = False
set_in_app(frame, False)


def normalize_stacktraces_for_grouping(data, grouping_config=None):
Expand All @@ -250,6 +250,13 @@ def normalize_stacktraces_for_grouping(data, grouping_config=None):
# unnecessarily.
for frames in stacktraces:
for frame in frames:
# Restore the original in_app value before the first grouping
# enhancers have been run. This allows to re-apply grouping
# enhancers on the original frame data.
orig_in_app = get_path(frame, 'data', 'orig_in_app')
if orig_in_app is not None:
frame['in_app'] = None if orig_in_app == -1 else bool(orig_in_app)

if frame.get('raw_function') is not None:
continue
raw_func = frame.get('function')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
created: '2019-04-30T08:25:08.407188Z'
created: '2019-06-05T20:07:56.986283Z'
creator: sentry
source: tests/sentry/event_manager/interfaces/test_exception.py
---
Expand Down Expand Up @@ -70,6 +70,8 @@ to_json:
stacktrace:
frames:
- abs_path: foo/baz.py
data:
orig_in_app: 1
filename: foo/baz.py
in_app: false
lineno: 1
Expand All @@ -79,6 +81,8 @@ to_json:
stacktrace:
frames:
- abs_path: foo/baz.py
data:
orig_in_app: 1
filename: foo/baz.py
in_app: false
lineno: 1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
created: '2019-05-16T11:55:53.361870Z'
created: '2019-06-05T11:45:12.934622Z'
creator: sentry
source: tests/sentry/grouping/test_variants.py
---
Expand Down Expand Up @@ -27,10 +27,10 @@ system:
system*
exception*
stacktrace*
frame (ignored because only 1 frame is considered by grouping enhancement rule (family:native))
frame (ignored because only 1 frame is considered by grouping enhancement rule (family:native max-frames=1))
function* (isolated function)
u'Scaleform::GFx::IME::GImeNamesManagerVista::OnActivated'
frame (ignored because only 1 frame is considered by grouping enhancement rule (family:native))
frame (ignored because only 1 frame is considered by grouping enhancement rule (family:native max-frames=1))
function* (isolated function)
u'Scaleform::GFx::AS3::IMEManager::DispatchEvent'
frame*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
created: '2019-05-10T11:49:03.468450Z'
created: '2019-06-05T11:45:13.045473Z'
creator: sentry
source: tests/sentry/grouping/test_variants.py
---
Expand Down Expand Up @@ -64,7 +64,7 @@ app:
u'bound_func'
lineno (line number is used only if module or filename are available)
25
frame (ignored by grouping enhancement rule (path:**/release_webhook.py - ranged))
frame (ignored by grouping enhancement rule (path:**/release_webhook.py v-group))
module*
u'sentry.web.frontend.release_webhook'
filename (module takes precedence)
Expand Down Expand Up @@ -130,7 +130,7 @@ system:
system*
exception*
stacktrace*
frame (ignored by grouping enhancement rule (path:**/release_webhook.py - ranged))
frame (ignored by grouping enhancement rule (path:**/release_webhook.py v-group))
module*
u'django.core.handlers.base'
filename (module takes precedence)
Expand All @@ -141,7 +141,7 @@ system:
u'get_response'
lineno (line number is used only if module or filename are available)
112
frame (ignored by grouping enhancement rule (path:**/release_webhook.py - ranged))
frame (ignored by grouping enhancement rule (path:**/release_webhook.py v-group))
module*
u'django.views.generic.base'
filename (module takes precedence)
Expand All @@ -152,7 +152,7 @@ system:
u'view'
lineno (line number is used only if module or filename are available)
69
frame (ignored by grouping enhancement rule (path:**/release_webhook.py - ranged))
frame (ignored by grouping enhancement rule (path:**/release_webhook.py v-group))
module*
u'django.utils.decorators'
filename (module takes precedence)
Expand All @@ -163,7 +163,7 @@ system:
u'_wrapper'
lineno (line number is used only if module or filename are available)
29
frame (ignored by grouping enhancement rule (path:**/release_webhook.py - ranged))
frame (ignored by grouping enhancement rule (path:**/release_webhook.py v-group))
module*
u'django.views.decorators.csrf'
filename (module takes precedence)
Expand All @@ -174,7 +174,7 @@ system:
u'wrapped_view'
lineno (line number is used only if module or filename are available)
57
frame (ignored by grouping enhancement rule (path:**/release_webhook.py - ranged))
frame (ignored by grouping enhancement rule (path:**/release_webhook.py v-group))
module*
u'django.utils.decorators'
filename (module takes precedence)
Expand All @@ -185,7 +185,7 @@ system:
u'bound_func'
lineno (line number is used only if module or filename are available)
25
frame (ignored by grouping enhancement rule (path:**/release_webhook.py - ranged))
frame (ignored by grouping enhancement rule (path:**/release_webhook.py v-group))
module*
u'sentry.web.frontend.release_webhook'
filename (module takes precedence)
Expand All @@ -196,7 +196,7 @@ system:
u'dispatch'
lineno (line number is used only if module or filename are available)
37
frame (ignored by grouping enhancement rule (path:**/release_webhook.py - ranged))
frame (ignored by grouping enhancement rule (path:**/release_webhook.py v-group))
module*
u'django.views.generic.base'
filename (module takes precedence)
Expand Down
Loading