Skip to content

feat(profiling): Drop deprecated profiling sdks data #91804

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 3 commits into from
May 16, 2025
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
37 changes: 7 additions & 30 deletions src/sentry/api/endpoints/organization_sdk_deprecations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@
from typing import DefaultDict, TypedDict

import sentry_sdk
from packaging.version import InvalidVersion, Version
from packaging.version import InvalidVersion
from packaging.version import parse as parse_version
from rest_framework import serializers
from rest_framework.request import Request
from rest_framework.response import Response

from sentry import options
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
from sentry.api.bases.organization import OrganizationEndpoint
from sentry.models.project import Project
from sentry.models.projectsdk import EventType, ProjectSDK
from sentry.models.projectsdk import EventType, ProjectSDK, get_minimum_sdk_version


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


MINIMUM_SDK_VERSION_OPTIONS: dict[tuple[int, str], str] = {
(EventType.PROFILE_CHUNK.value, "sentry.cocoa"): "sdk-deprecation.profile-chunk.cocoa",
(EventType.PROFILE_CHUNK.value, "sentry.python"): "sdk-deprecation.profile-chunk.python",
}


def get_minimum_sdk_version(project_sdk: ProjectSDK) -> Version | None:
parts = project_sdk.sdk_name.split(".", 2)
if len(parts) < 2:
return None

sdk_name = ".".join(parts[:2])

sdk_version_option = MINIMUM_SDK_VERSION_OPTIONS.get((project_sdk.event_type, sdk_name))
if sdk_version_option is None:
return None

sdk_version = options.get(sdk_version_option)
if sdk_version:
try:
return parse_version(sdk_version)
except InvalidVersion as e:
sentry_sdk.capture_exception(e)
return None


def get_deprecation_status(project_sdk: ProjectSDK) -> SDKDeprecation | None:
try:
sdk_version = parse_version(project_sdk.sdk_version)
except InvalidVersion as e:
sentry_sdk.capture_exception(e)
return None

minimum_sdk_version = get_minimum_sdk_version(project_sdk)
minimum_sdk_version = get_minimum_sdk_version(
project_sdk.event_type,
project_sdk.sdk_name,
hard_limit=False,
)

# no minimum sdk version was specified
if minimum_sdk_version is None:
Expand Down
2 changes: 2 additions & 0 deletions src/sentry/features/temporary.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ def register_temporary_features(manager: FeatureManager):
manager.add("organizations:profiling-beta", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, api_expose=True)
# Enables monitoring for latest profiling sdk used
manager.add("organizations:profiling-sdks", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enables dropping of deprecated profiling sdks used
manager.add("organizations:profiling-deprecate-sdks", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enables production profiling in sentry browser application
manager.add("organizations:profiling-browser", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, api_expose=False)
# Enables separate differential flamegraph page
Expand Down
32 changes: 32 additions & 0 deletions src/sentry/models/projectsdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
from collections.abc import Sequence
from enum import Enum

import sentry_sdk
from django.db import models
from packaging.version import InvalidVersion, Version
from packaging.version import parse as parse_version

from sentry import options
from sentry.backup.scopes import RelocationScope
from sentry.db.models import BoundedIntegerField, FlexibleForeignKey, region_silo_model, sane_repr
from sentry.db.models.base import DefaultFieldsModel
Expand Down Expand Up @@ -170,3 +172,33 @@ def normalize_sdk_name(sdk_name: str) -> str | None:
return sdk_name

return None


MINIMUM_SDK_VERSION_OPTIONS: dict[tuple[int, str], str] = {
(EventType.PROFILE_CHUNK.value, "sentry.cocoa"): "sdk-deprecation.profile-chunk.cocoa",
(EventType.PROFILE_CHUNK.value, "sentry.python"): "sdk-deprecation.profile-chunk.python",
}


def get_minimum_sdk_version(event_type: int, sdk_name: str, hard_limit: bool) -> Version | None:
parts = sdk_name.split(".", 2)
if len(parts) < 2:
return None

normalized_sdk_name = ".".join(parts[:2])

sdk_version_option = MINIMUM_SDK_VERSION_OPTIONS.get((event_type, normalized_sdk_name))
if sdk_version_option is None:
return None

if hard_limit:
sdk_version = options.get(f"{sdk_version_option}.hard")
else:
sdk_version = options.get(sdk_version_option)

if sdk_version:
try:
return parse_version(sdk_version)
except InvalidVersion as e:
sentry_sdk.capture_exception(e)
return None
10 changes: 10 additions & 0 deletions src/sentry/options/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -3194,8 +3194,18 @@
default="2.24.1",
flags=FLAG_AUTOMATOR_MODIFIABLE,
)
register(
"sdk-deprecation.profile-chunk.python.hard",
default="2.24.1",
flags=FLAG_AUTOMATOR_MODIFIABLE,
)
register(
"sdk-deprecation.profile-chunk.cocoa",
default="8.49.2",
flags=FLAG_AUTOMATOR_MODIFIABLE,
)
register(
"sdk-deprecation.profile-chunk.cocoa.hard",
default="8.49.0",
flags=FLAG_AUTOMATOR_MODIFIABLE,
)
Expand Down
86 changes: 65 additions & 21 deletions src/sentry/profiles/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from arroyo import Topic as ArroyoTopic
from arroyo.backends.kafka import KafkaPayload, KafkaProducer, build_kafka_configuration
from django.conf import settings
from packaging.version import InvalidVersion
from packaging.version import parse as parse_version

from sentry import features, options, quotas
from sentry.conf.types.kafka_definition import Topic
Expand All @@ -27,7 +29,7 @@
from sentry.models.files.utils import get_profiles_storage
from sentry.models.organization import Organization
from sentry.models.project import Project
from sentry.models.projectsdk import EventType, ProjectSDK
from sentry.models.projectsdk import EventType, ProjectSDK, get_minimum_sdk_version
from sentry.profiles.java import (
convert_android_methods_to_jvm_frames,
deobfuscate_signature,
Expand Down Expand Up @@ -159,6 +161,45 @@ def process_profile_task(
sentry_sdk.set_tag("project", project.id)
sentry_sdk.set_tag("project.slug", project.slug)

if sampled:
if features.has("organizations:profiling-sdks", organization):
try:
event_type = determine_profile_type(profile)
sdk_name, sdk_version = determine_client_sdk(profile, event_type)

ProjectSDK.update_with_newest_version_or_create(
project=project,
event_type=event_type,
sdk_name=sdk_name,
sdk_version=sdk_version,
)

# Check to see if the data is coming from an deprecated SDK
# and drop it if needed
if is_sdk_deprecated(event_type, sdk_name, sdk_version):
if features.has("organizations:profiling-deprecate-sdks", organization):
category = (
DataCategory.PROFILE_CHUNK
if event_type == EventType.PROFILE_CHUNK
else DataCategory.PROFILE
)
_track_outcome(
profile=profile,
project=project,
outcome=Outcome.FILTERED,
categories=[category],
reason="deprecated sdk",
)
return
except UnableToAcquireLock:
# unable to acquire the lock means another event is trying to
# update the version so we can skip the update from this event
pass
except (UnknownClientSDKException, UnknownProfileTypeException):
pass
except Exception as e:
sentry_sdk.capture_exception(e)

profile_context = {
"organization_id": profile["organization_id"],
"project_id": profile["project_id"],
Expand Down Expand Up @@ -236,14 +277,6 @@ def process_profile_task(

if sampled:
with metrics.timer("process_profile.track_outcome.accepted"):
if features.has("organizations:profiling-sdks", organization):
try:
track_latest_sdk(project, profile)
except (UnknownClientSDKException, UnknownProfileTypeException):
pass
except Exception as e:
sentry_sdk.capture_exception(e)

if not project.flags.has_profiles:
first_profile_received.send_robust(project=project, sender=Project)
try:
Expand Down Expand Up @@ -1212,21 +1245,32 @@ def determine_client_sdk(profile: Profile, event_type: EventType) -> tuple[str,
raise UnknownClientSDKException


def track_latest_sdk(project: Project, profile: Profile) -> None:
event_type = determine_profile_type(profile)
sdk_name, sdk_version = determine_client_sdk(profile, event_type)
def is_sdk_deprecated(event_type: EventType, sdk_name: str, sdk_version: str) -> bool:
minimum_version = get_minimum_sdk_version(event_type.value, sdk_name, hard_limit=True)

# no minimum sdk version was specified
if minimum_version is None:
return False

try:
ProjectSDK.update_with_newest_version_or_create(
project=project,
event_type=event_type,
sdk_name=sdk_name,
sdk_version=sdk_version,
version = parse_version(sdk_version)
except InvalidVersion:
return False

# satisfies the minimum sdk version
if version >= minimum_version:
return False

parts = sdk_name.split(".", 2)
if len(parts) >= 2:
normalized_sdk_name = ".".join(parts[:2])
metrics.incr(
"process_profile.sdk.deprecated",
tags={"sdk_name": normalized_sdk_name},
sample_rate=1.0,
)
except UnableToAcquireLock:
# unable to acquire the lock means another event is trying to update the version
# so we can skip the update from this event
pass

return True


@metrics.wraps("process_profile.process_vroomrs_profile")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def test_mixed_sdks(self):
project=self.project,
event_type=EventType.PROFILE_CHUNK.value,
sdk_name="sentry.cocoa",
sdk_version="8.49.0",
sdk_version="8.49.2",
)
response = self.client.get(
self.url,
Expand Down
68 changes: 63 additions & 5 deletions tests/sentry/profiles/test_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from sentry.profiles.utils import Profile
from sentry.testutils.cases import TransactionTestCase
from sentry.testutils.factories import Factories, get_fixture_path
from sentry.testutils.helpers import Feature
from sentry.testutils.helpers import Feature, override_options
from sentry.testutils.pytest.fixtures import django_db_all
from sentry.testutils.skips import requires_symbolicator
from sentry.utils import json
Expand Down Expand Up @@ -206,7 +206,7 @@ def sample_v1_profile():
},
"client_sdk": {
"name": "sentry.python",
"version": "2.23.0"
"version": "2.24.1"
}
}"""
)
Expand Down Expand Up @@ -273,7 +273,7 @@ def generate_sample_v2_profile():
},
"client_sdk": {
"name": "sentry.python",
"version": "2.23.0"
"version": "2.24.1"
}
}"""
)
Expand Down Expand Up @@ -976,7 +976,7 @@ def test_track_latest_sdk(
project=project,
event_type=event_type.value,
sdk_name="sentry.python",
sdk_version="2.23.0",
sdk_version="2.24.1",
)
is not None
)
Expand Down Expand Up @@ -1079,7 +1079,65 @@ def test_track_latest_sdk_with_payload(
project=project,
event_type=EventType.PROFILE.value,
sdk_name="sentry.python",
sdk_version="2.23.0",
sdk_version="2.24.1",
)
is not None
)


@patch("sentry.profiles.task._track_outcome")
@patch("sentry.profiles.task._push_profile_to_vroom")
@django_db_all
@pytest.mark.parametrize(
["profile", "category", "sdk_version", "dropped"],
[
pytest.param("sample_v1_profile", DataCategory.PROFILE, "2.23.0", False),
pytest.param("sample_v2_profile", DataCategory.PROFILE_CHUNK, "2.23.0", True),
pytest.param("sample_v2_profile", DataCategory.PROFILE_CHUNK, "2.24.0", False),
pytest.param("sample_v2_profile", DataCategory.PROFILE_CHUNK, "2.24.1", False),
],
)
def test_deprecated_sdks(
_push_profile_to_vroom,
_track_outcome,
profile,
category,
sdk_version,
dropped,
organization,
project,
request,
):
profile = request.getfixturevalue(profile)
profile["organization_id"] = organization.id
profile["project_id"] = project.id
profile["client_sdk"] = {
"name": "sentry.python",
"version": sdk_version,
}

with Feature(
[
"organizations:profiling-sdks",
"organizations:profiling-deprecate-sdks",
]
):
with override_options(
{
"sdk-deprecation.profile-chunk.python": "2.24.1",
"sdk-deprecation.profile-chunk.python.hard": "2.24.0",
}
):
process_profile_task(profile=profile)

if dropped:
_push_profile_to_vroom.assert_not_called()
_track_outcome.assert_called_with(
profile=profile,
project=project,
outcome=Outcome.FILTERED,
categories=[category],
reason="deprecated sdk",
)
else:
_push_profile_to_vroom.assert_called()
Loading