Skip to content
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
9 changes: 7 additions & 2 deletions src/sentry/api/bases/organization_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ def get_dataset(self, request: Request) -> Any:
return result

def get_snuba_dataclass(
self, request: Request, organization: Organization, check_global_views: bool = True
self,
request: Request,
organization: Organization,
check_global_views: bool = True,
quantize_date_params: bool = True,
) -> tuple[SnubaParams, ParamsType]:
"""This will eventually replace the get_snuba_params function"""
with sentry_sdk.start_span(op="discover.endpoint", description="filter_params(dataclass)"):
Expand All @@ -104,7 +108,8 @@ def get_snuba_dataclass(
)

filter_params: dict[str, Any] = self.get_filter_params(request, organization)
filter_params = self.quantize_date_params(request, filter_params)
if quantize_date_params:
filter_params = self.quantize_date_params(request, filter_params)
params = SnubaParams(
start=filter_params["start"],
end=filter_params["end"],
Expand Down
29 changes: 16 additions & 13 deletions src/sentry/api/endpoints/organization_profiling_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ def get(self, request: Request, organization: Organization) -> HttpResponse:
if not features.has(
"organizations:continuous-profiling-compat", organization, actor=request.user
):
params = self.get_snuba_params(request, organization)
project_ids = params["project_id"]
snuba_params, _ = self.get_snuba_dataclass(request, organization)

project_ids = snuba_params.project_ids
if len(project_ids) > 1:
raise ParseError(detail="You cannot get a flamegraph from multiple projects.")

Expand All @@ -79,12 +80,12 @@ def get(self, request: Request, organization: Organization) -> HttpResponse:
organization.id,
project_ids[0],
function_fingerprint,
params,
snuba_params,
request.GET.get("query", ""),
)
else:
sentry_sdk.set_tag("dataset", "profiles")
profile_ids = get_profile_ids(params, request.query_params.get("query", None))
profile_ids = get_profile_ids(snuba_params, request.query_params.get("query", None))

return proxy_profiling_service(
method="POST",
Expand Down Expand Up @@ -125,26 +126,28 @@ def get(self, request: Request, organization: Organization) -> HttpResponse:
return Response(status=404)

# We disable the date quantizing here because we need the timestamps to be precise.
params = self.get_snuba_params(request, organization, quantize_date_params=False)
snuba_params, _ = self.get_snuba_dataclass(
request, organization, quantize_date_params=False
)

project_ids = params.get("project_id")
project_ids = snuba_params.project_ids
if project_ids is None or len(project_ids) != 1:
raise ParseError(detail="one project_id must be specified.")

profiler_id = request.query_params.get("profiler_id")
if profiler_id is None:
raise ParseError(detail="profiler_id must be specified.")

chunk_ids = get_chunk_ids(params, profiler_id, project_ids[0])
chunk_ids = get_chunk_ids(snuba_params, profiler_id, project_ids[0])

return proxy_profiling_service(
method="POST",
path=f"/organizations/{organization.id}/projects/{project_ids[0]}/chunks",
json_data={
"profiler_id": profiler_id,
"chunk_ids": chunk_ids,
"start": str(int(params["start"].timestamp() * 1e9)),
"end": str(int(params["end"].timestamp() * 1e9)),
"start": str(int(snuba_params.start_date.timestamp() * 1e9)),
"end": str(int(snuba_params.end_date.timestamp() * 1e9)),
},
)

Expand All @@ -155,10 +158,10 @@ def get(self, request: Request, organization: Organization) -> HttpResponse:
if not features.has("organizations:profiling", organization, actor=request.user):
return Response(status=404)

params = self.get_snuba_params(request, organization)
snuba_params, _ = self.get_snuba_dataclass(request, organization)

project_ids = params.get("project_id")
if project_ids is None or len(project_ids) != 1:
project_ids = snuba_params.project_ids
if len(project_ids) != 1:
raise ParseError(detail="one project_id must be specified.")

span_group = request.query_params.get("span_group")
Expand All @@ -168,7 +171,7 @@ def get(self, request: Request, organization: Organization) -> HttpResponse:
spans = get_spans_from_group(
organization.id,
project_ids[0],
params,
snuba_params,
span_group,
)

Expand Down
18 changes: 10 additions & 8 deletions src/sentry/profiles/flamegraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from sentry.search.events.builder.discover import DiscoverQueryBuilder
from sentry.search.events.builder.profile_functions import ProfileFunctionsQueryBuilder
from sentry.search.events.fields import resolve_datetime64
from sentry.search.events.types import ParamsType, QueryBuilderConfig, SnubaParams
from sentry.search.events.types import QueryBuilderConfig, SnubaParams
from sentry.snuba import functions
from sentry.snuba.dataset import Dataset, EntityKey, StorageKey
from sentry.snuba.referrer import Referrer
Expand All @@ -42,12 +42,13 @@ class ProfileIds(TypedDict):


def get_profile_ids(
params: ParamsType,
snuba_params: SnubaParams,
query: str | None = None,
) -> ProfileIds:
builder = DiscoverQueryBuilder(
dataset=Dataset.Discover,
params=params,
params={},
snuba_params=snuba_params,
query=query,
selected_columns=["profile.id"],
limit=options.get("profiling.flamegraph.profile-set.size"),
Expand All @@ -69,15 +70,16 @@ def get_profiles_with_function(
organization_id: int,
project_id: int,
function_fingerprint: int,
params: ParamsType,
snuba_params: SnubaParams,
query: str,
) -> ProfileIds:
conditions = [query, f"fingerprint:{function_fingerprint}"]

result = functions.query(
selected_columns=["timestamp", "unique_examples()"],
query=" ".join(cond for cond in conditions if cond),
params=params,
params={},
snuba_params=snuba_params,
limit=100,
orderby=["-timestamp"],
referrer=Referrer.API_PROFILING_FUNCTION_SCOPED_FLAMEGRAPH.value,
Expand Down Expand Up @@ -113,7 +115,7 @@ class IntervalMetadata(TypedDict):
def get_spans_from_group(
organization_id: int,
project_id: int,
params: ParamsType,
snuba_params: SnubaParams,
span_group: str,
) -> dict[str, list[IntervalMetadata]]:
query = Query(
Expand Down Expand Up @@ -152,8 +154,8 @@ def get_spans_from_group(
],
where=[
Condition(Column("project_id"), Op.EQ, project_id),
Condition(Column("timestamp"), Op.GTE, params["start"]),
Condition(Column("timestamp"), Op.LT, params["end"]),
Condition(Column("timestamp"), Op.GTE, snuba_params.start),
Condition(Column("timestamp"), Op.LT, snuba_params.end),
Condition(Column("group"), Op.EQ, span_group),
Condition(Column("profiler_id"), Op.NEQ, ""),
],
Expand Down
10 changes: 5 additions & 5 deletions src/sentry/profiles/profile_chunks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

from sentry import options
from sentry.search.events.fields import resolve_datetime64
from sentry.search.events.types import ParamsType
from sentry.search.events.types import SnubaParams
from sentry.snuba.dataset import Dataset, StorageKey
from sentry.snuba.referrer import Referrer
from sentry.utils.snuba import raw_snql_query


def get_chunk_ids(
params: ParamsType,
snuba_params: SnubaParams,
profiler_id: str,
project_id: int,
) -> list[str]:
Expand All @@ -24,12 +24,12 @@ def get_chunk_ids(
Condition(
Column("end_timestamp"),
Op.GTE,
resolve_datetime64(params.get("start")),
resolve_datetime64(snuba_params.start),
),
Condition(
Column("start_timestamp"),
Op.LT,
resolve_datetime64(params.get("end")),
resolve_datetime64(snuba_params.end),
),
Condition(Column("project_id"), Op.EQ, project_id),
Condition(Column("profiler_id"), Op.EQ, profiler_id),
Expand All @@ -44,7 +44,7 @@ def get_chunk_ids(
query=query,
tenant_ids={
"referrer": Referrer.API_PROFILING_CONTINUOUS_PROFILING_FLAMECHART.value,
"organization_id": params["organization_id"],
"organization_id": snuba_params.organization_id,
},
)

Expand Down
14 changes: 14 additions & 0 deletions src/sentry/search/events/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from snuba_sdk.function import CurriedFunction, Function
from snuba_sdk.orderby import OrderBy

from sentry.exceptions import InvalidSearchQuery
from sentry.models.environment import Environment
from sentry.models.organization import Organization
from sentry.models.project import Project
Expand Down Expand Up @@ -105,6 +106,19 @@ def parse_stats_period(self) -> None:

self.start = get_datetime_from_stats_period(self.stats_period, self.end)

@property
def start_date(self) -> datetime:
# This and end_date are helper functions so callers don't have to check if either are defined for typing
if self.start is None:
raise InvalidSearchQuery("start is required")
return self.start
Comment on lines +109 to +114
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not hugely in love with this, might revisit it later to see if i can make it cleaner


@property
def end_date(self) -> datetime:
if self.end is None:
raise InvalidSearchQuery("end is required")
return self.end

@property
def environment_names(self) -> list[str]:
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_more_than_one_project(self):
assert response.status_code == 400, response.data
assert response.data == {
"detail": ErrorDetail(
"You cannot view events from multiple projects.",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the name of this test, i think this is the correct error message?

"You cannot get a flamegraph from multiple projects.",
code="parse_error",
),
}
Expand Down
37 changes: 19 additions & 18 deletions tests/sentry/profiles/test_flamegraph.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import timedelta, timezone

from sentry.profiles.flamegraph import get_profiles_with_function
from sentry.search.events.types import SnubaParams
from sentry.testutils.cases import ProfilesSnubaTestCase
from sentry.testutils.helpers.datetime import before_now
from sentry.utils.samples import load_data
Expand Down Expand Up @@ -69,12 +70,12 @@ def test_get_profile_with_function(self):
self.organization.id,
self.project.id,
self.function_fingerprint({"package": "foo", "function": "foo"}),
{
"organization_id": self.organization.id,
"project_id": [self.project.id],
"start": before_now(days=1),
"end": self.now,
},
SnubaParams(
organization=self.organization,
projects=[self.project],
start=before_now(days=1),
end=self.now,
),
"",
)
assert len(profile_ids["profile_ids"]) == 4, profile_ids
Expand All @@ -84,12 +85,12 @@ def test_get_profile_with_function_with_transaction_filter(self):
self.organization.id,
self.project.id,
self.function_fingerprint({"package": "foo", "function": "foo"}),
{
"organization_id": self.organization.id,
"project_id": [self.project.id],
"start": before_now(days=1),
"end": self.now,
},
SnubaParams(
organization=self.organization,
projects=[self.project],
start=before_now(days=1),
end=self.now,
),
"transaction:foobar",
)
assert len(profile_ids["profile_ids"]) == 1, profile_ids
Expand All @@ -99,12 +100,12 @@ def test_get_profile_with_function_no_match(self):
self.organization.id,
self.project.id,
self.function_fingerprint({"package": "foo", "function": "foo"}),
{
"organization_id": self.organization.id,
"project_id": [self.project.id],
"start": before_now(days=1),
"end": self.now,
},
SnubaParams(
organization=self.organization,
projects=[self.project],
start=before_now(days=1),
end=self.now,
),
"transaction:foo",
)
assert len(profile_ids["profile_ids"]) == 0, profile_ids