|
@@ -7,7 +7,10 @@ from collections import defaultdict
|
|
|
from symbolic import ProguardMapper # type: ignore
|
|
|
|
|
|
from sentry import features
|
|
|
-from sentry.issues.grouptype import PerformanceFileIOMainThreadGroupType
|
|
|
+from sentry.issues.grouptype import (
|
|
|
+ PerformanceDBMainThreadGroupType,
|
|
|
+ PerformanceFileIOMainThreadGroupType,
|
|
|
+)
|
|
|
from sentry.models import Organization, Project, ProjectDebugFile
|
|
|
|
|
|
from ..base import DetectorType, PerformanceDetector, total_span_time
|
|
@@ -15,7 +18,52 @@ from ..performance_problem import PerformanceProblem
|
|
|
from ..types import Span
|
|
|
|
|
|
|
|
|
-class FileIOMainThreadDetector(PerformanceDetector):
|
|
|
+class BaseIOMainThreadDetector(PerformanceDetector):
|
|
|
+ __slots__ = ("spans_involved", "stored_problems")
|
|
|
+
|
|
|
+ def init(self):
|
|
|
+ self.spans_involved = {}
|
|
|
+ self.most_recent_start_time = {}
|
|
|
+ self.most_recent_hash = {}
|
|
|
+ self.stored_problems = {}
|
|
|
+ self.mapper = None
|
|
|
+ self.parent_to_blocked_span = defaultdict(list)
|
|
|
+
|
|
|
+ def visit_span(self, span: Span):
|
|
|
+ if self._is_io_on_main_thread(span) and span.get("op", "").lower().startswith(
|
|
|
+ self.SPAN_PREFIX
|
|
|
+ ):
|
|
|
+ parent_span_id = span.get("parent_span_id")
|
|
|
+ self.parent_to_blocked_span[parent_span_id].append(span)
|
|
|
+
|
|
|
+ def on_complete(self):
|
|
|
+ for parent_span_id, span_list in self.parent_to_blocked_span.items():
|
|
|
+ span_list = [
|
|
|
+ span for span in span_list if "start_timestamp" in span and "timestamp" in span
|
|
|
+ ]
|
|
|
+ total_duration = total_span_time(span_list)
|
|
|
+ settings_for_span = self.settings_for_span(span_list[0])
|
|
|
+ if not settings_for_span:
|
|
|
+ return
|
|
|
+
|
|
|
+ _, _, _, _, settings = settings_for_span
|
|
|
+ if total_duration >= settings["duration_threshold"]:
|
|
|
+ fingerprint = self._fingerprint(span_list)
|
|
|
+ self.stored_problems[fingerprint] = PerformanceProblem(
|
|
|
+ fingerprint=fingerprint,
|
|
|
+ op=span_list[0].get("op"),
|
|
|
+ desc=span_list[0].get("description", ""),
|
|
|
+ parent_span_ids=[parent_span_id],
|
|
|
+ type=self.group_type,
|
|
|
+ cause_span_ids=[],
|
|
|
+ offender_span_ids=[span["span_id"] for span in span_list if "span_id" in span],
|
|
|
+ )
|
|
|
+
|
|
|
+ def is_creation_allowed_for_project(self, project: Project) -> bool:
|
|
|
+ return True
|
|
|
+
|
|
|
+
|
|
|
+class FileIOMainThreadDetector(BaseIOMainThreadDetector):
|
|
|
"""
|
|
|
Checks for a file io span on the main thread
|
|
|
"""
|
|
@@ -23,16 +71,13 @@ class FileIOMainThreadDetector(PerformanceDetector):
|
|
|
__slots__ = ("spans_involved", "stored_problems")
|
|
|
|
|
|
IGNORED_EXTENSIONS = {".nib", ".plist"}
|
|
|
+ SPAN_PREFIX = "file"
|
|
|
type: DetectorType = DetectorType.FILE_IO_MAIN_THREAD
|
|
|
settings_key = DetectorType.FILE_IO_MAIN_THREAD
|
|
|
+ group_type = PerformanceFileIOMainThreadGroupType
|
|
|
|
|
|
def init(self):
|
|
|
- self.spans_involved = {}
|
|
|
- self.most_recent_start_time = {}
|
|
|
- self.most_recent_hash = {}
|
|
|
- self.stored_problems = {}
|
|
|
- self.mapper = None
|
|
|
- self.parent_to_blocked_span = defaultdict(list)
|
|
|
+ super().init()
|
|
|
self._prepare_deobfuscation()
|
|
|
|
|
|
def _prepare_deobfuscation(self):
|
|
@@ -78,34 +123,6 @@ class FileIOMainThreadDetector(PerformanceDetector):
|
|
|
else:
|
|
|
return frame.get("function", "")
|
|
|
|
|
|
- def visit_span(self, span: Span):
|
|
|
- if self._is_file_io_on_main_thread(span):
|
|
|
- parent_span_id = span.get("parent_span_id")
|
|
|
- self.parent_to_blocked_span[parent_span_id].append(span)
|
|
|
-
|
|
|
- def on_complete(self):
|
|
|
- for parent_span_id, span_list in self.parent_to_blocked_span.items():
|
|
|
- span_list = [
|
|
|
- span for span in span_list if "start_timestamp" in span and "timestamp" in span
|
|
|
- ]
|
|
|
- total_duration = total_span_time(span_list)
|
|
|
- settings_for_span = self.settings_for_span(span_list[0])
|
|
|
- if not settings_for_span:
|
|
|
- return
|
|
|
-
|
|
|
- _, _, _, _, settings = settings_for_span
|
|
|
- if total_duration >= settings["duration_threshold"]:
|
|
|
- fingerprint = self._fingerprint(span_list)
|
|
|
- self.stored_problems[fingerprint] = PerformanceProblem(
|
|
|
- fingerprint=fingerprint,
|
|
|
- op=span_list[0].get("op"),
|
|
|
- desc=span_list[0].get("description", ""),
|
|
|
- parent_span_ids=[parent_span_id],
|
|
|
- type=PerformanceFileIOMainThreadGroupType,
|
|
|
- cause_span_ids=[],
|
|
|
- offender_span_ids=[span["span_id"] for span in span_list if "span_id" in span],
|
|
|
- )
|
|
|
-
|
|
|
def _fingerprint(self, span_list) -> str:
|
|
|
call_stack_strings = []
|
|
|
overall_stack = []
|
|
@@ -118,13 +135,11 @@ class FileIOMainThreadDetector(PerformanceDetector):
|
|
|
overall_stack.append(
|
|
|
".".join(sorted(set(call_stack_strings), key=lambda c: call_stack_strings.index(c)))
|
|
|
)
|
|
|
- call_stack = "-".join(
|
|
|
- sorted(set(overall_stack), key=lambda s: overall_stack.index(s))
|
|
|
- ).encode("utf8")
|
|
|
- hashed_stack = hashlib.sha1(call_stack).hexdigest()
|
|
|
+ call_stack = "-".join(sorted(set(overall_stack), key=lambda s: overall_stack.index(s)))
|
|
|
+ hashed_stack = hashlib.sha1(call_stack.encode("utf8")).hexdigest()
|
|
|
return f"1-{PerformanceFileIOMainThreadGroupType.type_id}-{hashed_stack}"
|
|
|
|
|
|
- def _is_file_io_on_main_thread(self, span: Span) -> bool:
|
|
|
+ def _is_io_on_main_thread(self, span: Span) -> bool:
|
|
|
data = span.get("data", {})
|
|
|
if data is None:
|
|
|
return False
|
|
@@ -139,5 +154,49 @@ class FileIOMainThreadDetector(PerformanceDetector):
|
|
|
"organizations:performance-file-io-main-thread-detector", organization, actor=None
|
|
|
)
|
|
|
|
|
|
+
|
|
|
+class DBMainThreadDetector(BaseIOMainThreadDetector):
|
|
|
+ """
|
|
|
+ Checks for a file io span on the main thread
|
|
|
+ """
|
|
|
+
|
|
|
+ __slots__ = ("spans_involved", "stored_problems")
|
|
|
+
|
|
|
+ SPAN_PREFIX = "db"
|
|
|
+ type: DetectorType = DetectorType.DB_MAIN_THREAD
|
|
|
+ settings_key = DetectorType.DB_MAIN_THREAD
|
|
|
+ group_type = PerformanceDBMainThreadGroupType
|
|
|
+
|
|
|
+ def init(self):
|
|
|
+ self.spans_involved = {}
|
|
|
+ self.most_recent_start_time = {}
|
|
|
+ self.most_recent_hash = {}
|
|
|
+ self.stored_problems = {}
|
|
|
+ self.mapper = None
|
|
|
+ self.parent_to_blocked_span = defaultdict(list)
|
|
|
+
|
|
|
+ def _fingerprint(self, span_list) -> str:
|
|
|
+ description_strings = []
|
|
|
+ for span in span_list:
|
|
|
+ description_strings.append(span.get("description"))
|
|
|
+ # Use set to remove dupes, and list index to preserve order
|
|
|
+ joined_queries = "-".join(
|
|
|
+ sorted(set(description_strings), key=lambda c: description_strings.index(c))
|
|
|
+ )
|
|
|
+ hashed_queries = hashlib.sha1(joined_queries.encode("utf8")).hexdigest()
|
|
|
+ return f"1-{PerformanceDBMainThreadGroupType.type_id}-{hashed_queries}"
|
|
|
+
|
|
|
+ def _is_io_on_main_thread(self, span: Span) -> bool:
|
|
|
+ data = span.get("data", {})
|
|
|
+ if data is None:
|
|
|
+ return False
|
|
|
+ # doing is True since the value can be any type
|
|
|
+ return data.get("blocked_main_thread", False) is True
|
|
|
+
|
|
|
+ def is_creation_allowed_for_organization(self, organization: Organization) -> bool:
|
|
|
+ return features.has(
|
|
|
+ "organizations:performance-db-main-thread-detector", organization, actor=None
|
|
|
+ )
|
|
|
+
|
|
|
def is_creation_allowed_for_project(self, project: Project) -> bool:
|
|
|
return True
|