Skip to content
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

feat(profiling): Process sample format #39106

Merged
merged 15 commits into from
Sep 27, 2022
6 changes: 3 additions & 3 deletions src/sentry/api/endpoints/organization_profiling_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
from sentry.api.utils import get_date_range_from_params
from sentry.exceptions import InvalidSearchQuery
from sentry.models import Organization
from sentry.utils import json
from sentry.utils.dates import get_interval_from_range, get_rollup_from_request, parse_stats_period
from sentry.utils.profiling import (
from sentry.profiles.utils import (
get_from_profiling_service,
parse_profile_filters,
proxy_profiling_service,
)
from sentry.utils import json
from sentry.utils.dates import get_interval_from_range, get_rollup_from_request, parse_stats_period


class OrganizationProfilingBaseEndpoint(OrganizationEventsV2EndpointBase): # type: ignore
Expand Down
4 changes: 2 additions & 2 deletions src/sentry/api/endpoints/project_profiling_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
from sentry.api.paginator import GenericOffsetPaginator
from sentry.exceptions import InvalidSearchQuery
from sentry.models import Project
from sentry.utils import json
from sentry.utils.profiling import (
from sentry.profiles.utils import (
get_from_profiling_service,
parse_profile_filters,
proxy_profiling_service,
)
from sentry.utils import json


class ProjectProfilingBaseEndpoint(ProjectEndpoint): # type: ignore
Expand Down
130 changes: 95 additions & 35 deletions src/sentry/profiles/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from datetime import datetime
from time import sleep, time
from typing import Any, List, Mapping, MutableMapping, Optional
from typing import Any, List, Mapping, MutableMapping, Optional, Tuple

import sentry_sdk
from django.conf import settings
Expand All @@ -15,12 +15,12 @@
from sentry.lang.native.symbolicator import Symbolicator
from sentry.models import Organization, Project, ProjectDebugFile
from sentry.profiles.device import classify_device
from sentry.profiles.utils import get_from_profiling_service
from sentry.signals import first_profile_received
from sentry.tasks.base import instrumented_task
from sentry.tasks.symbolication import RetrySymbolication
from sentry.utils import json, kafka_config, metrics
from sentry.utils.outcomes import Outcome, track_outcome
from sentry.utils.profiling import get_from_profiling_service
from sentry.utils.pubsub import KafkaPublisher

Profile = MutableMapping[str, Any]
Expand All @@ -45,7 +45,14 @@ def process_profile(

try:
if _should_symbolicate(profile):
_symbolicate(profile=profile, project=project)
modules, stacktraces = _prepare_frames_from_profile(profile)
stacktraces = _symbolicate(
project=project,
profile_id=profile["profile_id"],
modules=modules,
stacktraces=stacktraces,
)
_process_symbolicator_results(profile=profile, stacktraces=stacktraces)
except Exception as e:
sentry_sdk.capture_exception(e)
_track_outcome(
Expand Down Expand Up @@ -150,44 +157,36 @@ def _normalize(profile: Profile, organization: Organization) -> None:
)


@metrics.wraps("process_profile.symbolicate")
def _symbolicate(profile: Profile, project: Project) -> None:
symbolicator = Symbolicator(project=project, event_id=profile["profile_id"])
def _prepare_frames_from_profile(profile: Profile) -> Tuple[List[Any], List[Any]]:
modules = profile["debug_meta"]["images"]
stacktraces = [
{
"registers": {},
"frames": s["frames"],
}
for s in profile["sampled_profile"]["samples"]
]

# in the sample format, we have a frames key containing all the frames
if "frames" in profile["profile"]:
stacktraces = [{"registers": {}, "frames": profile["profile"]["frames"]}]
# in the original format, we need to gather frames from all samples
else:
stacktraces = [
{
"registers": {},
"frames": s["frames"],
}
for s in profile["sampled_profile"]["samples"]
]
Zylphrex marked this conversation as resolved.
Show resolved Hide resolved
return (modules, stacktraces)


@metrics.wraps("process_profile.symbolicate.request")
def _symbolicate(
project: Project, profile_id: str, modules: List[Any], stacktraces: List[Any]
) -> List[Any]:
symbolicator = Symbolicator(project=project, event_id=profile_id)
symbolication_start_time = time()

while True:
try:
response = symbolicator.process_payload(stacktraces=stacktraces, modules=modules)

assert len(profile["sampled_profile"]["samples"]) == len(response["stacktraces"])

for original, symbolicated in zip(
profile["sampled_profile"]["samples"], response["stacktraces"]
):
for frame in symbolicated["frames"]:
frame.pop("pre_context", None)
frame.pop("context_line", None)
frame.pop("post_context", None)

# here we exclude the frames related to the profiler itself as we don't care to profile the profiler.
if (
profile["platform"] == "rust"
and len(symbolicated["frames"]) >= 2
and symbolicated["frames"][0].get("function", "") == "perf_signal_handler"
):
original["frames"] = symbolicated["frames"][2:]
else:
original["frames"] = symbolicated["frames"]
break
return symbolicator.process_payload(stacktraces=stacktraces, modules=modules).get(
"stacktraces", []
)
except RetrySymbolication as e:
if (
time() - symbolication_start_time
Expand All @@ -205,10 +204,71 @@ def _symbolicate(profile: Profile, project: Project) -> None:
sentry_sdk.capture_exception(e)
break


@metrics.wraps("process_profile.symbolicate.process")
def _process_symbolicator_results(profile: Profile, stacktraces: List[Any]):
if "version" in profile.get("profile", {}):
profile["profile"]["frames"] = stacktraces[0]["frames"]
_process_symbolicator_results_for_sample(profile, stacktraces)
return

if profile["platform"] == "rust":
_process_symbolicator_results_for_rust(profile, stacktraces)

# rename the profile key to suggest it has been processed
profile["profile"] = profile.pop("sampled_profile")


def _process_symbolicator_results_for_sample(profile: Profile, stacktraces: List[Any]):
if profile["platform"] == "rust":
for frame in stacktraces[0]["frames"]:
frame.pop("pre_context", None)
frame.pop("context_line", None)
frame.pop("post_context", None)

def truncate_stack_needed(frame):
return frame.get("function", "") == "perf_signal_handler"

elif profile["platform"] == "cocoa":

def truncate_stack_needed(frame):
return frame.get("instruction_addr", "") == "0xffffffffc"

else:

def truncate_stack_needed(frame):
return False

for sample in profile["profile"]["samples"]:
stack_id = sample["stack_id"]
stack = profile["profile"]["stacks"][stack_id]

if len(stack) < 2:
continue

frame = profile["profile"]["frames"][stack[-1]]

if truncate_stack_needed(frame):
profile["profile"]["stacks"][stack_id] = stack[:-2]
Zylphrex marked this conversation as resolved.
Show resolved Hide resolved


def _process_symbolicator_results_for_rust(profile: Profile, stacktraces: List[Any]):
for original, symbolicated in zip(profile["sampled_profile"]["samples"], stacktraces):
for frame in symbolicated["frames"]:
frame.pop("pre_context", None)
frame.pop("context_line", None)
frame.pop("post_context", None)

if len(symbolicated["frames"]) < 2:
continue

# here we exclude the frames related to the profiler itself as we don't care to profile the profiler.
if symbolicated["frames"][-1].get("function", "") == "perf_signal_handler":
original["frames"] = symbolicated["frames"][:-2]
Zylphrex marked this conversation as resolved.
Show resolved Hide resolved
else:
original["frames"] = symbolicated["frames"]


@metrics.wraps("process_profile.deobfuscate")
def _deobfuscate(profile: Profile, project: Project) -> None:
debug_file_id = profile.get("build_id")
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/sentry/utils/test_profiling.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from sentry.exceptions import InvalidSearchQuery
from sentry.utils.profiling import parse_profile_filters
from sentry.profiles.utils import parse_profile_filters


@pytest.mark.parametrize(
Expand Down