Skip to content

Commit 99d062b

Browse files
Merge branch 'master' into isabella/checkout-w-seer
2 parents 7dd38a1 + df14bba commit 99d062b

File tree

126 files changed

+2332
-1099
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

126 files changed

+2332
-1099
lines changed

.github/actions/setup-sentry/action.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,7 @@ runs:
5050
5151
### pytest configuration ###
5252
echo "PY_COLORS=1" >> "$GITHUB_ENV"
53-
# XXX(epurkihser): We've raised fail-slow from 60s to 120s since
54-
# 2025-05, our migration dependency tree has grown too large and it
55-
# takes these tests longer than 60 seconds to run. Once we've flattened
56-
# our migrations we can bring this back down (asottile is working on
57-
# this)
58-
echo "PYTEST_ADDOPTS=--reruns=5 --durations=10 --fail-slow=120s" >> $GITHUB_ENV
53+
echo "PYTEST_ADDOPTS=--reruns=5 --durations=10 --fail-slow=60s" >> $GITHUB_ENV
5954
echo "COVERAGE_CORE=sysmon" >> "$GITHUB_ENV"
6055
6156
### pytest-sentry configuration ###

migrations_lockfile.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ hybridcloud: 0021_django_arrayfield_scope_list
1313

1414
insights: 0001_add_starred_transactions_model
1515

16-
monitors: 0004_record_date_in_progress_sql_only
16+
monitors: 0005_record_date_in_progress_state
1717

1818
nodestore: 0002_nodestore_no_dictfield
1919

src/sentry/celery.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ def holds_bad_pickle_object(value, memo=None):
3333
bad_object = holds_bad_pickle_object(item, memo)
3434
if bad_object is not None:
3535
return bad_object
36+
return
3637
elif isinstance(value, dict):
3738
for item in value.values():
3839
bad_object = holds_bad_pickle_object(item, memo)
3940
if bad_object is not None:
4041
return bad_object
41-
42+
return
4243
if isinstance(value, models.Model):
4344
return (
4445
value,
@@ -56,7 +57,7 @@ def holds_bad_pickle_object(value, memo=None):
5657
return None
5758
elif value is None:
5859
return None
59-
elif not isinstance(value, (dict, list, str, float, int, bool, tuple, frozenset)):
60+
elif not isinstance(value, (str, float, int, bool)):
6061
return value, "do not pickle stdlib classes"
6162
return None
6263

@@ -69,9 +70,9 @@ def good_use_of_pickle_or_bad_use_of_pickle(task, args, kwargs):
6970
if bad is not None:
7071
bad_object, reason = bad
7172
raise TypeError(
72-
"Task %r was invoked with an object that we do not want "
73+
"Task %s was invoked with an object that we do not want "
7374
"to pass via pickle (%r, reason is %s) in argument %s"
74-
% (task, bad_object, reason, name)
75+
% (task.name, bad_object, reason, name)
7576
)
7677

7778

src/sentry/grouping/enhancer/__init__.py

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,71 @@ def apply_category_and_updated_in_app_to_frames(
525525
),
526526
)
527527

528+
def assemble_stacktrace_component_legacy(
529+
self,
530+
variant_name: str,
531+
frame_components: list[FrameGroupingComponent],
532+
frames: list[dict[str, Any]],
533+
platform: str | None,
534+
exception_data: dict[str, Any] | None = None,
535+
) -> StacktraceGroupingComponent:
536+
"""
537+
This assembles a `stacktrace` grouping component out of the given
538+
`frame` components and source frames.
539+
540+
This also handles cases where the entire stacktrace should be discarded.
541+
"""
542+
543+
match_frames: list[Any] = [create_match_frame(frame, platform) for frame in frames]
544+
rust_frames = [RustFrame(contributes=c.contributes) for c in frame_components]
545+
rust_exception_data = make_rust_exception_data(exception_data)
546+
547+
# Modify the rust frames by applying +group/-group rules and getting hints for both those
548+
# changes and the `in_app` changes applied by earlier in the ingestion process by
549+
# `apply_category_and_updated_in_app_to_frames`. Also, get `hint` and `contributes` values
550+
# for the overall stacktrace (returned in `rust_results`).
551+
rust_stacktrace_results = self.rust_enhancements.assemble_stacktrace_component(
552+
match_frames, rust_exception_data, rust_frames
553+
)
554+
555+
# Tally the number of each type of frame in the stacktrace. Later on, this will allow us to
556+
# both collect metrics and use the information in decisions about whether to send the event
557+
# to Seer
558+
frame_counts: Counter[str] = Counter()
559+
560+
# Update frame components with results from rust
561+
for frame, frame_component, rust_frame in zip(frames, frame_components, rust_frames):
562+
rust_contributes = bool(rust_frame.contributes) # bool-ing this for mypy's sake
563+
rust_hint = rust_frame.hint
564+
rust_hint_type = (
565+
None
566+
if rust_hint is None
567+
else "in-app" if rust_hint.startswith("marked") else "contributes"
568+
)
569+
570+
hint = get_hint_for_frame(variant_name, frame, frame_component, rust_frame)
571+
572+
if not (variant_name == "system" and rust_hint_type == "in-app"):
573+
hint = rust_hint
574+
575+
frame_component.update(contributes=rust_contributes, hint=hint)
576+
577+
# Add this frame to our tally
578+
key = f"{"in_app" if frame_component.in_app else "system"}_{"contributing" if frame_component.contributes else "non_contributing"}_frames"
579+
frame_counts[key] += 1
580+
581+
stacktrace_contributes = rust_stacktrace_results.contributes
582+
stacktrace_hint = rust_stacktrace_results.hint
583+
584+
stacktrace_component = StacktraceGroupingComponent(
585+
values=frame_components,
586+
hint=stacktrace_hint,
587+
contributes=stacktrace_contributes,
588+
frame_counts=frame_counts,
589+
)
590+
591+
return stacktrace_component
592+
528593
def assemble_stacktrace_component(
529594
self,
530595
variant_name: str,
@@ -611,21 +676,12 @@ def assemble_stacktrace_component(
611676
in_app_rust_frames,
612677
contributes_rust_frames,
613678
):
614-
frame_type = "in-app" if frame_component.in_app else "system"
615-
rust_contributes = bool(rust_frame.contributes) # bool-ing this for mypy's sake
616-
rust_hint = rust_frame.hint
617-
rust_hint_type = (
618-
None
619-
if rust_hint is None
620-
else "in-app" if rust_hint.startswith("marked") else "contributes"
621-
)
622-
623679
# System frames should never contribute in the app variant, so if that's what we have,
624680
# force `contribtues=False`, regardless of the rust results
625-
if variant_name == "app" and frame_type == "system":
681+
if variant_name == "app" and not frame_component.in_app:
626682
contributes = False
627683
else:
628-
contributes = rust_contributes
684+
contributes = rust_frame.contributes
629685

630686
hint = get_hint_for_frame(variant_name, frame, frame_component, rust_frame)
631687
if self.run_split_enhancements:
@@ -640,15 +696,6 @@ def assemble_stacktrace_component(
640696
variant_name, frame, frame_component, contributes_rust_frame, "contributes"
641697
)
642698

643-
# TODO: Remove this workaround once we remove the legacy config. It's done this way (as
644-
# a second pass at setting the values that undoes what the first pass did, rather than
645-
# being incorporated into the first pass) so that we won't have to change any of the
646-
# main logic when we remove it.
647-
if self.bases and self.bases[0].startswith("legacy"):
648-
contributes = rust_contributes
649-
if not (variant_name == "system" and rust_hint_type == "in-app"):
650-
hint = rust_hint
651-
652699
frame_component.update(contributes=contributes, hint=hint)
653700

654701
# Add this frame to our tally
@@ -690,13 +737,7 @@ def assemble_stacktrace_component(
690737
# stacktrace to be wrong, too (if in the process of ignoring rust we turn a stacktrace with
691738
# at least one contributing frame into one without any). So we need to special-case here as
692739
# well.
693-
#
694-
# TODO: Remove the first condition once we get rid of the legacy config
695-
if (
696-
not (self.bases and self.bases[0].startswith("legacy"))
697-
and variant_name == "app"
698-
and frame_counts["in_app_contributing_frames"] == 0
699-
):
740+
if variant_name == "app" and frame_counts["in_app_contributing_frames"] == 0:
700741
stacktrace_contributes = False
701742
stacktrace_hint = None
702743
else:

src/sentry/grouping/strategies/legacy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ def stacktrace_legacy(
452452
frames_for_filtering.append(frame.get_raw_data())
453453
prev_frame = frame
454454

455-
stacktrace_component = context.config.enhancements.assemble_stacktrace_component(
455+
stacktrace_component = context.config.enhancements.assemble_stacktrace_component_legacy(
456456
variant_name, frame_components, frames_for_filtering, event.platform
457457
)
458458
stacktrace_component.update(contributes=contributes, hint=hint)

src/sentry/incidents/logic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ def get_metric_issue_aggregates(
415415
"incidents.get_incident_aggregates.snql.query.error",
416416
tags={
417417
"dataset": params.snuba_query.dataset,
418-
"entity": EntityKey.EAPSpans.value,
418+
"entity": EntityKey.EAPItemsSpan.value,
419419
},
420420
)
421421
raise

src/sentry/incidents/subscription_processor.py

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -408,33 +408,34 @@ def process_update(self, subscription_update: QuerySubscriptionUpdate) -> None:
408408
},
409409
)
410410

411-
if features.has(
412-
"organizations:workflow-engine-metric-alert-processing",
413-
self.subscription.project.organization,
414-
):
415-
packet = MetricDetectorUpdate(
416-
entity=subscription_update.get("entity", ""),
417-
subscription_id=subscription_update["subscription_id"],
418-
values={"value": aggregation_value},
419-
timestamp=self.last_update,
420-
)
421-
data_packet = DataPacket[MetricDetectorUpdate](
422-
source_id=str(self.subscription.id), packet=packet
423-
)
424-
results = process_data_packets([data_packet], DATA_SOURCE_SNUBA_QUERY_SUBSCRIPTION)
411+
if aggregation_value is not None:
425412
if features.has(
426-
"organizations:workflow-engine-metric-alert-dual-processing-logs",
427-
self.alert_rule.organization,
413+
"organizations:workflow-engine-metric-alert-processing",
414+
self.subscription.project.organization,
428415
):
429-
logger.info(
430-
"dual processing results for alert rule",
431-
extra={
432-
"results": results,
433-
"num_results": len(results),
434-
"value": aggregation_value,
435-
"rule_id": self.alert_rule.id,
436-
},
416+
packet = MetricDetectorUpdate(
417+
entity=subscription_update.get("entity", ""),
418+
subscription_id=subscription_update["subscription_id"],
419+
values={"value": aggregation_value},
420+
timestamp=self.last_update,
421+
)
422+
data_packet = DataPacket[MetricDetectorUpdate](
423+
source_id=str(self.subscription.id), packet=packet
437424
)
425+
results = process_data_packets([data_packet], DATA_SOURCE_SNUBA_QUERY_SUBSCRIPTION)
426+
if features.has(
427+
"organizations:workflow-engine-metric-alert-dual-processing-logs",
428+
self.alert_rule.organization,
429+
):
430+
logger.info(
431+
"dual processing results for alert rule",
432+
extra={
433+
"results": results,
434+
"num_results": len(results),
435+
"value": aggregation_value,
436+
"rule_id": self.alert_rule.id,
437+
},
438+
)
438439

439440
has_anomaly_detection = features.has(
440441
"organizations:anomaly-detection-alerts", self.subscription.project.organization

src/sentry/integrations/jira/utils/create_issue_schema_transformers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,10 @@ def transform_fields(
9898
type_transformers = get_type_transformer_mappings(user_id_field)
9999
custom_field_transformers = get_custom_field_transformer_mappings()
100100

101+
lowercased_data = {k.lower(): v for k, v in data.items()}
102+
101103
for field in jira_fields:
102-
field_data = data.get(field.key)
104+
field_data = lowercased_data.get(field.key.lower())
103105

104106
# Skip any values that indicate no value should be provided.
105107
# We have some older alert templates with "" values, which will raise

src/sentry/monitors/consumers/monitor_consumer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,13 +828,17 @@ def _process_checkin(item: CheckinItem, txn: Transaction | Span) -> None:
828828
# to UTC
829829
clock_time = item.ts.replace(tzinfo=UTC)
830830

831+
# Record the reported in_progress time when the check is in progress
832+
date_in_progress = start_time if status == CheckInStatus.IN_PROGRESS else None
833+
831834
check_in, created = MonitorCheckIn.objects.get_or_create(
832835
defaults={
833836
"duration": duration,
834837
"status": status,
835838
"date_added": start_time,
836839
"date_updated": start_time,
837840
"date_clock": clock_time,
841+
"date_in_progress": date_in_progress,
838842
"expected_time": expected_time,
839843
"timeout_at": timeout_at,
840844
"monitor_config": monitor_config,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Generated by Django 5.2.1 on 2025-05-15 16:51
2+
3+
from django.db import migrations, models
4+
from django.db.migrations import SeparateDatabaseAndState
5+
6+
from sentry.new_migrations.migrations import CheckedMigration
7+
8+
9+
class Migration(CheckedMigration):
10+
# This flag is used to mark that a migration shouldn't be automatically run in production.
11+
# This should only be used for operations where it's safe to run the migration after your
12+
# code has deployed. So this should not be used for most operations that alter the schema
13+
# of a table.
14+
# Here are some things that make sense to mark as post deployment:
15+
# - Large data migrations. Typically we want these to be run manually so that they can be
16+
# monitored and not block the deploy for a long period of time while they run.
17+
# - Adding indexes to large tables. Since this can take a long time, we'd generally prefer to
18+
# run this outside deployments so that we don't block them. Note that while adding an index
19+
# is a schema change, it's completely safe to run the operation after the code has deployed.
20+
# Once deployed, run these manually via: https://develop.sentry.dev/database-migrations/#migration-deployment
21+
22+
is_post_deployment = False
23+
24+
dependencies = [
25+
("monitors", "0004_record_date_in_progress_sql_only"),
26+
]
27+
28+
operations = [
29+
SeparateDatabaseAndState(
30+
database_operations=[],
31+
state_operations=[
32+
migrations.AddField(
33+
model_name="monitorcheckin",
34+
name="date_in_progress",
35+
field=models.DateTimeField(null=True),
36+
)
37+
],
38+
)
39+
]

src/sentry/monitors/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,12 @@ class MonitorCheckIn(Model):
495495
as detecting misses)
496496
"""
497497

498+
date_in_progress = models.DateTimeField(null=True)
499+
"""
500+
Records the time when the first in_progress check-in was received by relay.
501+
If no in_progress check-in was ever sent this will remain null.
502+
"""
503+
498504
expected_time = models.DateTimeField(null=True)
499505
"""
500506
Holds the exact time we expected to receive this check-in

src/sentry/monitors/serializers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ class MonitorCheckInSerializerResponse(MonitorCheckInSerializerResponseOptional)
278278
dateCreated: datetime
279279
dateAdded: datetime
280280
dateUpdated: datetime
281+
dateInProgress: datetime | None
281282
dateClock: datetime
282283
expectedTime: datetime
283284
monitorConfig: MonitorConfigSerializerResponse
@@ -348,6 +349,7 @@ def serialize(self, obj, attrs, user, **kwargs) -> MonitorCheckInSerializerRespo
348349
"dateAdded": obj.date_added,
349350
"dateUpdated": obj.date_updated,
350351
"dateClock": obj.date_clock,
352+
"dateInProgress": obj.date_in_progress,
351353
"expectedTime": obj.expected_time,
352354
"monitorConfig": cast(MonitorConfigSerializerResponse, config),
353355
}

src/sentry/options/defaults.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3107,6 +3107,12 @@
31073107
)
31083108

31093109
# Taskbroker flags
3110+
register(
3111+
"taskworker.try_compress.profile_metrics",
3112+
default=0.0,
3113+
type=Float,
3114+
flags=FLAG_AUTOMATOR_MODIFIABLE,
3115+
)
31103116

31113117
register(
31123118
"taskworker.route.overrides",

0 commit comments

Comments
 (0)