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
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ dependencies = [
"sentry-ophio>=1.1.3",
# sentry-options is only used in getsentry for now
"sentry-options>=1.0.13",
"sentry-protos>=0.17.0",
"sentry-protos>=0.21.0",
"sentry-redis-tools>=0.5.0",
"sentry-relay>=0.9.27",
"sentry-scm==0.20.0",
Expand All @@ -111,7 +111,7 @@ dependencies = [
"statsd>=3.3.0",
"structlog>=22.1.0",
"symbolic>=12.14.1",
"taskbroker-client>=0.17.0",
"taskbroker-client>=0.17.1",
"tiktoken>=0.8.0",
"tokenizers>=0.22.0",
"tldextract>=5.1.2",
Expand Down
202 changes: 3 additions & 199 deletions src/sentry/api/endpoints/organization_events_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import abc
import logging
from collections import defaultdict, deque
from collections.abc import Callable, Iterable, Mapping, Sequence
from collections.abc import Mapping, Sequence
from concurrent.futures import as_completed
from datetime import datetime, timedelta
from typing import Any, Optional, TypedDict, TypeVar, cast
from typing import Any, Optional, TypedDict, cast

import sentry_sdk
from django.http import Http404, HttpRequest, HttpResponse
from django.http import HttpRequest, HttpResponse
from rest_framework.exceptions import ParseError
from rest_framework.request import Request
from rest_framework.response import Response
Expand Down Expand Up @@ -48,7 +48,6 @@
MAX_TRACE_SIZE: int = 100


_T = TypeVar("_T")
NodeSpans = list[dict[str, Any]]
SnubaSpan = TypedDict(
"SnubaSpan",
Expand Down Expand Up @@ -530,14 +529,6 @@ def find_timestamp_params(transactions: Sequence[SnubaTransaction]) -> dict[str,
}


def find_event(
items: Iterable[_T | None],
function: Callable[[_T | None], Any],
default: _T | None = None,
) -> _T | None:
return next(filter(function, items), default)


def is_root(item: SnubaTransaction) -> bool:
return item.get("root", "0") == "1"

Expand Down Expand Up @@ -1078,193 +1069,6 @@ def serialize(
raise NotImplementedError


@cell_silo_endpoint
class OrganizationEventsTraceLightEndpoint(OrganizationEventsTraceEndpointBase):
publish_status = {
"GET": ApiPublishStatus.PRIVATE,
}

@staticmethod
def get_current_transaction(
transactions: Sequence[SnubaTransaction],
errors: Sequence[SnubaError],
event_id: str,
) -> tuple[SnubaTransaction | None, Event | GroupEvent | None]:
"""Given an event_id return the related transaction event

The event_id could be for an error, since we show the quick-trace
for both event types
We occasionally have to get the nodestore data, so this function returns
the nodestore event as well so that we're doing that in one location.
"""
transaction_event = find_event(
transactions, lambda item: item is not None and item["id"] == event_id
)
if transaction_event is not None:
return transaction_event, eventstore.backend.get_event_by_id(
transaction_event["project.id"], transaction_event["id"]
)

# The event couldn't be found, it might be an error
error_event = find_event(errors, lambda item: item is not None and item["id"] == event_id)
# Alright so we're looking at an error, time to see if we can find its transaction
if error_event is not None:
# Unfortunately the only association from an event back to its transaction is name & span_id
# First maybe we got lucky and the error happened on the transaction's "span"
error_span = error_event["trace.span"]
transaction_event = find_event(
transactions, lambda item: item is not None and item["trace.span"] == error_span
)
if transaction_event is not None:
return transaction_event, eventstore.backend.get_event_by_id(
transaction_event["project.id"], transaction_event["id"]
)
# We didn't get lucky, time to talk to nodestore...
for transaction_event in transactions:
if transaction_event["transaction"] != error_event["transaction"]:
continue

nodestore_event = eventstore.backend.get_event_by_id(
transaction_event["project.id"], transaction_event["id"]
)
if nodestore_event is None:
return None, None
transaction_spans: NodeSpans = nodestore_event.data.get("spans", [])
for span in transaction_spans:
if span["span_id"] == error_event["trace.span"]:
return transaction_event, nodestore_event

return None, None

def serialize(
self,
limit: int,
transactions: Sequence[SnubaTransaction],
errors: Sequence[SnubaError],
roots: Sequence[SnubaTransaction],
warning_extra: dict[str, str],
event_id: str | None,
detailed: bool = False,
query_source: QuerySource | None = None,
) -> dict[str, list[LightResponse | TraceError]]:
"""Because the light endpoint could potentially have gaps between root and event we return a flattened list"""
if event_id is None:
raise ParseError(detail="An event_id is required for the light trace")
snuba_event, nodestore_event = self.get_current_transaction(transactions, errors, event_id)
parent_map = self.construct_parent_map(transactions)
error_map = self.construct_error_map(errors)
trace_results: list[TraceEvent] = []
current_generation: int | None = None
root_id: str | None = None

with sentry_sdk.start_span(op="building.trace", name="light trace"):
# Check if the event is an orphan_error
if not snuba_event or not nodestore_event:
orphan_error = find_event(
errors, lambda item: item is not None and item["id"] == event_id
)
if orphan_error:
return {
"transactions": [],
"orphan_errors": [self.serialize_error(orphan_error)],
}
else:
# The current event couldn't be found in errors or transactions
raise Http404()

# Going to nodestore is more expensive than looping twice so check if we're on the root first
for root in roots:
if root["id"] == snuba_event["id"]:
current_generation = 0
break

snuba_params = self.get_snuba_params(self.request, self.request.organization)
if current_generation is None:
for root in roots:
# We might not be necessarily connected to the root if we're on an orphan event
if root["id"] != snuba_event["id"]:
# Get the root event and see if the current event's span is in the root event
root_event = eventstore.backend.get_event_by_id(
root["project.id"], root["id"]
)
if root_event is None:
root_spans: NodeSpans = []
else:
root_spans = root_event.data.get("spans", [])
root_span = find_event(
root_spans,
lambda item: item is not None
and item["span_id"] == snuba_event["trace.parent_span"],
)

# We only know to add the root if its the direct parent
if root_span is not None:
# For the light response, the parent will be unknown unless it is a direct descendent of the root
root_id = root["id"]
trace_results.append(
TraceEvent(
root,
None,
0,
True,
snuba_params=snuba_params,
query_source=query_source,
)
)
current_generation = 1
break

current_event = TraceEvent(
snuba_event,
root_id,
current_generation,
True,
snuba_params=snuba_params,
query_source=query_source,
)
trace_results.append(current_event)

spans: NodeSpans = nodestore_event.data.get("spans", [])
# Need to include the transaction as a span as well
#
# Important that we left pad the span id with 0s because
# the span id is stored as an UInt64 and converted into
# a hex string when quering. However, the conversion does
# not ensure that the final span id is 16 chars long since
# it's a naive base 10 to base 16 conversion.
spans.append({"span_id": snuba_event["trace.span"].rjust(16, "0")})

for span in spans:
if span["span_id"] in error_map:
current_event.errors.extend(
[self.serialize_error(error) for error in error_map.pop(span["span_id"])]
)
if span["span_id"] in parent_map:
child_events = parent_map.pop(span["span_id"])
trace_results.extend(
[
TraceEvent(
child_event,
snuba_event["id"],
(
current_event.generation + 1
if current_event.generation is not None
else None
),
True,
snuba_params=snuba_params,
query_source=query_source,
)
for child_event in child_events
]
)

return {
"transactions": [result.to_dict() for result in trace_results],
"orphan_errors": [],
}


@cell_silo_endpoint
class OrganizationEventsTraceEndpoint(OrganizationEventsTraceEndpointBase):
@staticmethod
Expand Down
25 changes: 3 additions & 22 deletions src/sentry/api/endpoints/organization_trace_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,6 @@ class SerializedResponse(TypedDict, total=False):
transactionChildCountMap: SnubaData
spansCountMap: dict[str, int]

# These are deprecated
logs: int
errors: int
performance_issues: int
span_count: int
transaction_child_count_map: SnubaData
span_count_map: dict[str, int]
uptime_checks: int # Only present when include_uptime is True


def extract_uptime_count(uptime_result: list[TraceItemTableResponse]) -> int:
"""Safely extract uptime count from query result."""
Expand Down Expand Up @@ -282,31 +273,21 @@ def serialize(
"spansCountMap": {
row["span.op"]: row["count()"] for row in results["spans_op_count"]["data"]
},
# these are deprecated
"logs": results["logs_meta"]["data"][0].get("count()") or 0,
"errors": errors_count,
"performance_issues": perf_issues,
"span_count": results["spans_meta"]["data"][0].get("count()") or 0,
"transaction_child_count_map": results["transaction_children"]["data"],
"span_count_map": {
row["span.op"]: row["count()"] for row in results["spans_op_count"]["data"]
},
}

sentry_sdk.metrics.distribution(
"performance.trace.logs.count",
response["logs"],
response["logsCount"],
)
sentry_sdk.metrics.distribution(
"performance.trace.span.count",
response["span_count"],
response["spansCount"],
)
sentry_sdk.metrics.distribution(
"performance.trace.errors.count",
response["errors"],
response["errorsCount"],
)

if uptime_count is not None:
response["uptime_checks"] = uptime_count
response["uptimeCount"] = uptime_count
return response
6 changes: 0 additions & 6 deletions src/sentry/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,6 @@
from .endpoints.organization_events_timeseries import OrganizationEventsTimeseriesEndpoint
from .endpoints.organization_events_trace import (
OrganizationEventsTraceEndpoint,
OrganizationEventsTraceLightEndpoint,
OrganizationEventsTraceMetaEndpoint,
)
from .endpoints.organization_events_trends import (
Expand Down Expand Up @@ -1900,11 +1899,6 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]:
OrganizationEventsNewTrendsStatsEndpoint.as_view(),
name="sentry-api-0-organization-events-trends-statsv2",
),
re_path(
r"^(?P<organization_id_or_slug>[^/]+)/events-trace-light/(?P<trace_id>(?:\d+|[A-Fa-f0-9-]{32,36}))/$",
OrganizationEventsTraceLightEndpoint.as_view(),
name="sentry-api-0-organization-events-trace-light",
),
re_path(
r"^(?P<organization_id_or_slug>[^/]+)/events-trace/(?P<trace_id>(?:\d+|[A-Fa-f0-9-]{32,36}))/$",
OrganizationEventsTraceEndpoint.as_view(),
Expand Down
Loading
Loading