Skip to content

Commit 9712b9a

Browse files
Zylphrexandrewshie-sentry
authored andcommitted
feat(profiling): Drop deprecated profiling sdks data (#91804)
This will start dropping profiling data from deprecated sdks when the flag is enabled.
1 parent f96e09e commit 9712b9a

File tree

7 files changed

+180
-57
lines changed

7 files changed

+180
-57
lines changed

src/sentry/api/endpoints/organization_sdk_deprecations.py

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@
22
from typing import DefaultDict, TypedDict
33

44
import sentry_sdk
5-
from packaging.version import InvalidVersion, Version
5+
from packaging.version import InvalidVersion
66
from packaging.version import parse as parse_version
77
from rest_framework import serializers
88
from rest_framework.request import Request
99
from rest_framework.response import Response
1010

11-
from sentry import options
1211
from sentry.api.api_owners import ApiOwner
1312
from sentry.api.api_publish_status import ApiPublishStatus
1413
from sentry.api.base import region_silo_endpoint
1514
from sentry.api.bases.organization import OrganizationEndpoint
1615
from sentry.models.project import Project
17-
from sentry.models.projectsdk import EventType, ProjectSDK
16+
from sentry.models.projectsdk import EventType, ProjectSDK, get_minimum_sdk_version
1817

1918

2019
class SDKDeprecationsSerializer(serializers.Serializer):
@@ -93,40 +92,18 @@ def get_event_types(raw_event_type: str) -> list[EventType]:
9392
raise ValueError(f"Unknown event type: {raw_event_type}")
9493

9594

96-
MINIMUM_SDK_VERSION_OPTIONS: dict[tuple[int, str], str] = {
97-
(EventType.PROFILE_CHUNK.value, "sentry.cocoa"): "sdk-deprecation.profile-chunk.cocoa",
98-
(EventType.PROFILE_CHUNK.value, "sentry.python"): "sdk-deprecation.profile-chunk.python",
99-
}
100-
101-
102-
def get_minimum_sdk_version(project_sdk: ProjectSDK) -> Version | None:
103-
parts = project_sdk.sdk_name.split(".", 2)
104-
if len(parts) < 2:
105-
return None
106-
107-
sdk_name = ".".join(parts[:2])
108-
109-
sdk_version_option = MINIMUM_SDK_VERSION_OPTIONS.get((project_sdk.event_type, sdk_name))
110-
if sdk_version_option is None:
111-
return None
112-
113-
sdk_version = options.get(sdk_version_option)
114-
if sdk_version:
115-
try:
116-
return parse_version(sdk_version)
117-
except InvalidVersion as e:
118-
sentry_sdk.capture_exception(e)
119-
return None
120-
121-
12295
def get_deprecation_status(project_sdk: ProjectSDK) -> SDKDeprecation | None:
12396
try:
12497
sdk_version = parse_version(project_sdk.sdk_version)
12598
except InvalidVersion as e:
12699
sentry_sdk.capture_exception(e)
127100
return None
128101

129-
minimum_sdk_version = get_minimum_sdk_version(project_sdk)
102+
minimum_sdk_version = get_minimum_sdk_version(
103+
project_sdk.event_type,
104+
project_sdk.sdk_name,
105+
hard_limit=False,
106+
)
130107

131108
# no minimum sdk version was specified
132109
if minimum_sdk_version is None:

src/sentry/features/temporary.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ def register_temporary_features(manager: FeatureManager):
273273
manager.add("organizations:profiling-beta", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, api_expose=True)
274274
# Enables monitoring for latest profiling sdk used
275275
manager.add("organizations:profiling-sdks", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
276+
# Enables dropping of deprecated profiling sdks used
277+
manager.add("organizations:profiling-deprecate-sdks", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
276278
# Enables production profiling in sentry browser application
277279
manager.add("organizations:profiling-browser", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, api_expose=False)
278280
# Enables separate differential flamegraph page

src/sentry/models/projectsdk.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
from collections.abc import Sequence
55
from enum import Enum
66

7+
import sentry_sdk
78
from django.db import models
89
from packaging.version import InvalidVersion, Version
910
from packaging.version import parse as parse_version
1011

12+
from sentry import options
1113
from sentry.backup.scopes import RelocationScope
1214
from sentry.db.models import BoundedIntegerField, FlexibleForeignKey, region_silo_model, sane_repr
1315
from sentry.db.models.base import DefaultFieldsModel
@@ -170,3 +172,33 @@ def normalize_sdk_name(sdk_name: str) -> str | None:
170172
return sdk_name
171173

172174
return None
175+
176+
177+
MINIMUM_SDK_VERSION_OPTIONS: dict[tuple[int, str], str] = {
178+
(EventType.PROFILE_CHUNK.value, "sentry.cocoa"): "sdk-deprecation.profile-chunk.cocoa",
179+
(EventType.PROFILE_CHUNK.value, "sentry.python"): "sdk-deprecation.profile-chunk.python",
180+
}
181+
182+
183+
def get_minimum_sdk_version(event_type: int, sdk_name: str, hard_limit: bool) -> Version | None:
184+
parts = sdk_name.split(".", 2)
185+
if len(parts) < 2:
186+
return None
187+
188+
normalized_sdk_name = ".".join(parts[:2])
189+
190+
sdk_version_option = MINIMUM_SDK_VERSION_OPTIONS.get((event_type, normalized_sdk_name))
191+
if sdk_version_option is None:
192+
return None
193+
194+
if hard_limit:
195+
sdk_version = options.get(f"{sdk_version_option}.hard")
196+
else:
197+
sdk_version = options.get(sdk_version_option)
198+
199+
if sdk_version:
200+
try:
201+
return parse_version(sdk_version)
202+
except InvalidVersion as e:
203+
sentry_sdk.capture_exception(e)
204+
return None

src/sentry/options/defaults.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3194,8 +3194,18 @@
31943194
default="2.24.1",
31953195
flags=FLAG_AUTOMATOR_MODIFIABLE,
31963196
)
3197+
register(
3198+
"sdk-deprecation.profile-chunk.python.hard",
3199+
default="2.24.1",
3200+
flags=FLAG_AUTOMATOR_MODIFIABLE,
3201+
)
31973202
register(
31983203
"sdk-deprecation.profile-chunk.cocoa",
3204+
default="8.49.2",
3205+
flags=FLAG_AUTOMATOR_MODIFIABLE,
3206+
)
3207+
register(
3208+
"sdk-deprecation.profile-chunk.cocoa.hard",
31993209
default="8.49.0",
32003210
flags=FLAG_AUTOMATOR_MODIFIABLE,
32013211
)

src/sentry/profiles/task.py

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from arroyo import Topic as ArroyoTopic
1616
from arroyo.backends.kafka import KafkaPayload, KafkaProducer, build_kafka_configuration
1717
from django.conf import settings
18+
from packaging.version import InvalidVersion
19+
from packaging.version import parse as parse_version
1820

1921
from sentry import features, options, quotas
2022
from sentry.conf.types.kafka_definition import Topic
@@ -27,7 +29,7 @@
2729
from sentry.models.files.utils import get_profiles_storage
2830
from sentry.models.organization import Organization
2931
from sentry.models.project import Project
30-
from sentry.models.projectsdk import EventType, ProjectSDK
32+
from sentry.models.projectsdk import EventType, ProjectSDK, get_minimum_sdk_version
3133
from sentry.profiles.java import (
3234
convert_android_methods_to_jvm_frames,
3335
deobfuscate_signature,
@@ -159,6 +161,45 @@ def process_profile_task(
159161
sentry_sdk.set_tag("project", project.id)
160162
sentry_sdk.set_tag("project.slug", project.slug)
161163

164+
if sampled:
165+
if features.has("organizations:profiling-sdks", organization):
166+
try:
167+
event_type = determine_profile_type(profile)
168+
sdk_name, sdk_version = determine_client_sdk(profile, event_type)
169+
170+
ProjectSDK.update_with_newest_version_or_create(
171+
project=project,
172+
event_type=event_type,
173+
sdk_name=sdk_name,
174+
sdk_version=sdk_version,
175+
)
176+
177+
# Check to see if the data is coming from an deprecated SDK
178+
# and drop it if needed
179+
if is_sdk_deprecated(event_type, sdk_name, sdk_version):
180+
if features.has("organizations:profiling-deprecate-sdks", organization):
181+
category = (
182+
DataCategory.PROFILE_CHUNK
183+
if event_type == EventType.PROFILE_CHUNK
184+
else DataCategory.PROFILE
185+
)
186+
_track_outcome(
187+
profile=profile,
188+
project=project,
189+
outcome=Outcome.FILTERED,
190+
categories=[category],
191+
reason="deprecated sdk",
192+
)
193+
return
194+
except UnableToAcquireLock:
195+
# unable to acquire the lock means another event is trying to
196+
# update the version so we can skip the update from this event
197+
pass
198+
except (UnknownClientSDKException, UnknownProfileTypeException):
199+
pass
200+
except Exception as e:
201+
sentry_sdk.capture_exception(e)
202+
162203
profile_context = {
163204
"organization_id": profile["organization_id"],
164205
"project_id": profile["project_id"],
@@ -236,14 +277,6 @@ def process_profile_task(
236277

237278
if sampled:
238279
with metrics.timer("process_profile.track_outcome.accepted"):
239-
if features.has("organizations:profiling-sdks", organization):
240-
try:
241-
track_latest_sdk(project, profile)
242-
except (UnknownClientSDKException, UnknownProfileTypeException):
243-
pass
244-
except Exception as e:
245-
sentry_sdk.capture_exception(e)
246-
247280
if not project.flags.has_profiles:
248281
first_profile_received.send_robust(project=project, sender=Project)
249282
try:
@@ -1212,21 +1245,32 @@ def determine_client_sdk(profile: Profile, event_type: EventType) -> tuple[str,
12121245
raise UnknownClientSDKException
12131246

12141247

1215-
def track_latest_sdk(project: Project, profile: Profile) -> None:
1216-
event_type = determine_profile_type(profile)
1217-
sdk_name, sdk_version = determine_client_sdk(profile, event_type)
1248+
def is_sdk_deprecated(event_type: EventType, sdk_name: str, sdk_version: str) -> bool:
1249+
minimum_version = get_minimum_sdk_version(event_type.value, sdk_name, hard_limit=True)
1250+
1251+
# no minimum sdk version was specified
1252+
if minimum_version is None:
1253+
return False
12181254

12191255
try:
1220-
ProjectSDK.update_with_newest_version_or_create(
1221-
project=project,
1222-
event_type=event_type,
1223-
sdk_name=sdk_name,
1224-
sdk_version=sdk_version,
1256+
version = parse_version(sdk_version)
1257+
except InvalidVersion:
1258+
return False
1259+
1260+
# satisfies the minimum sdk version
1261+
if version >= minimum_version:
1262+
return False
1263+
1264+
parts = sdk_name.split(".", 2)
1265+
if len(parts) >= 2:
1266+
normalized_sdk_name = ".".join(parts[:2])
1267+
metrics.incr(
1268+
"process_profile.sdk.deprecated",
1269+
tags={"sdk_name": normalized_sdk_name},
1270+
sample_rate=1.0,
12251271
)
1226-
except UnableToAcquireLock:
1227-
# unable to acquire the lock means another event is trying to update the version
1228-
# so we can skip the update from this event
1229-
pass
1272+
1273+
return True
12301274

12311275

12321276
@metrics.wraps("process_profile.process_vroomrs_profile")

tests/sentry/api/endpoints/test_organization_sdk_deprecations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def test_mixed_sdks(self):
139139
project=self.project,
140140
event_type=EventType.PROFILE_CHUNK.value,
141141
sdk_name="sentry.cocoa",
142-
sdk_version="8.49.0",
142+
sdk_version="8.49.2",
143143
)
144144
response = self.client.get(
145145
self.url,

tests/sentry/profiles/test_task.py

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from sentry.profiles.utils import Profile
3434
from sentry.testutils.cases import TransactionTestCase
3535
from sentry.testutils.factories import Factories, get_fixture_path
36-
from sentry.testutils.helpers import Feature
36+
from sentry.testutils.helpers import Feature, override_options
3737
from sentry.testutils.pytest.fixtures import django_db_all
3838
from sentry.testutils.skips import requires_symbolicator
3939
from sentry.utils import json
@@ -206,7 +206,7 @@ def sample_v1_profile():
206206
},
207207
"client_sdk": {
208208
"name": "sentry.python",
209-
"version": "2.23.0"
209+
"version": "2.24.1"
210210
}
211211
}"""
212212
)
@@ -273,7 +273,7 @@ def generate_sample_v2_profile():
273273
},
274274
"client_sdk": {
275275
"name": "sentry.python",
276-
"version": "2.23.0"
276+
"version": "2.24.1"
277277
}
278278
}"""
279279
)
@@ -976,7 +976,7 @@ def test_track_latest_sdk(
976976
project=project,
977977
event_type=event_type.value,
978978
sdk_name="sentry.python",
979-
sdk_version="2.23.0",
979+
sdk_version="2.24.1",
980980
)
981981
is not None
982982
)
@@ -1079,7 +1079,65 @@ def test_track_latest_sdk_with_payload(
10791079
project=project,
10801080
event_type=EventType.PROFILE.value,
10811081
sdk_name="sentry.python",
1082-
sdk_version="2.23.0",
1082+
sdk_version="2.24.1",
10831083
)
10841084
is not None
10851085
)
1086+
1087+
1088+
@patch("sentry.profiles.task._track_outcome")
1089+
@patch("sentry.profiles.task._push_profile_to_vroom")
1090+
@django_db_all
1091+
@pytest.mark.parametrize(
1092+
["profile", "category", "sdk_version", "dropped"],
1093+
[
1094+
pytest.param("sample_v1_profile", DataCategory.PROFILE, "2.23.0", False),
1095+
pytest.param("sample_v2_profile", DataCategory.PROFILE_CHUNK, "2.23.0", True),
1096+
pytest.param("sample_v2_profile", DataCategory.PROFILE_CHUNK, "2.24.0", False),
1097+
pytest.param("sample_v2_profile", DataCategory.PROFILE_CHUNK, "2.24.1", False),
1098+
],
1099+
)
1100+
def test_deprecated_sdks(
1101+
_push_profile_to_vroom,
1102+
_track_outcome,
1103+
profile,
1104+
category,
1105+
sdk_version,
1106+
dropped,
1107+
organization,
1108+
project,
1109+
request,
1110+
):
1111+
profile = request.getfixturevalue(profile)
1112+
profile["organization_id"] = organization.id
1113+
profile["project_id"] = project.id
1114+
profile["client_sdk"] = {
1115+
"name": "sentry.python",
1116+
"version": sdk_version,
1117+
}
1118+
1119+
with Feature(
1120+
[
1121+
"organizations:profiling-sdks",
1122+
"organizations:profiling-deprecate-sdks",
1123+
]
1124+
):
1125+
with override_options(
1126+
{
1127+
"sdk-deprecation.profile-chunk.python": "2.24.1",
1128+
"sdk-deprecation.profile-chunk.python.hard": "2.24.0",
1129+
}
1130+
):
1131+
process_profile_task(profile=profile)
1132+
1133+
if dropped:
1134+
_push_profile_to_vroom.assert_not_called()
1135+
_track_outcome.assert_called_with(
1136+
profile=profile,
1137+
project=project,
1138+
outcome=Outcome.FILTERED,
1139+
categories=[category],
1140+
reason="deprecated sdk",
1141+
)
1142+
else:
1143+
_push_profile_to_vroom.assert_called()

0 commit comments

Comments
 (0)