14
14
from rest_framework .request import Request
15
15
from rest_framework .response import Response
16
16
from sentry_relay .consts import SPAN_STATUS_CODE_TO_NAME
17
- from snuba_sdk import Column , Condition , Function , Op
17
+ from snuba_sdk import Column , Function
18
18
19
19
from sentry import constants , eventstore , features , options
20
20
from sentry .api .api_publish_status import ApiPublishStatus
29
29
from sentry .models .project import Project
30
30
from sentry .organizations .services .organization import RpcOrganization
31
31
from sentry .search .events .builder .discover import DiscoverQueryBuilder
32
- from sentry .search .events .builder .spans_indexed import SpansIndexedQueryBuilder
33
32
from sentry .search .events .types import QueryBuilderConfig , SnubaParams
34
33
from sentry .snuba .dataset import Dataset
35
34
from sentry .snuba .query_sources import QuerySource
36
35
from sentry .snuba .referrer import Referrer
37
- from sentry .utils .iterators import chunked
38
36
from sentry .utils .numbers import base32_encode , format_grouped_length
39
37
from sentry .utils .sdk import set_span_attribute
40
38
from sentry .utils .snuba import bulk_snuba_queries
@@ -594,7 +592,6 @@ def query_trace_data(
594
592
transaction_params : SnubaParams ,
595
593
limit : int ,
596
594
event_id : str | None ,
597
- use_spans : bool ,
598
595
query_source : QuerySource | None = QuerySource .SENTRY_BACKEND ,
599
596
) -> tuple [Sequence [SnubaTransaction ], Sequence [SnubaError ]]:
600
597
transaction_columns = [
@@ -627,13 +624,6 @@ def query_trace_data(
627
624
# Target is the event_id the frontend plans to render, we try to sort it to the top so it loads even if its not
628
625
# within the query limit, needs to be the first orderby cause it takes precedence over finding the root
629
626
transaction_orderby .insert (0 , "-target" )
630
- if use_spans :
631
- transaction_columns .extend (
632
- [
633
- "measurements.key" ,
634
- "measurements.value" ,
635
- ]
636
- )
637
627
transaction_query = DiscoverQueryBuilder (
638
628
Dataset .Transactions ,
639
629
params = {},
@@ -715,14 +705,6 @@ def query_trace_data(
715
705
result ["issue.ids" ] = occurrence_issue_ids .get (result ["id" ], [])
716
706
result ["occurrence_id" ] = occurrence_ids .get (result ["id" ], [])
717
707
result ["trace.parent_transaction" ] = None
718
- if use_spans :
719
- result ["measurements" ] = {
720
- key : {
721
- "value" : value ,
722
- "type" : transaction_query .get_field_type (f"measurements.{ key } " ),
723
- }
724
- for key , value in zip (result ["measurements.key" ], result ["measurements.value" ])
725
- }
726
708
727
709
# Snuba responses aren't typed
728
710
return cast (Sequence [SnubaTransaction ], transformed_results [0 ]), cast (
@@ -743,44 +725,6 @@ def strip_span_id(span_id):
743
725
return result
744
726
745
727
746
- def build_span_query (trace_id : str , spans_params : SnubaParams , query_spans : list [str ]):
747
- parents_query = SpansIndexedQueryBuilder (
748
- Dataset .SpansIndexed ,
749
- params = {},
750
- snuba_params = spans_params ,
751
- query = f"trace:{ trace_id } " ,
752
- selected_columns = [
753
- "transaction.id" ,
754
- "span_id" ,
755
- "precise.start_ts" ,
756
- "precise.finish_ts" ,
757
- ],
758
- # Don't add an orderby here that way if clickhouse hits the # of span_ids we've asked for it'll exit early
759
- limit = len (query_spans ),
760
- )
761
- # Performance improvement, snuba's parser is extremely slow when we're sending thousands of
762
- # span_ids here, using a `splitByChar` means that snuba will not parse the giant list of spans
763
- span_minimum = options .get ("performance.traces.span_query_minimum_spans" )
764
- set_span_attribute ("trace_view.spans.span_minimum" , span_minimum )
765
- sentry_sdk .set_tag ("trace_view.split_by_char.optimization" , len (query_spans ) > span_minimum )
766
- if len (query_spans ) > span_minimum :
767
- # TODO: because we're not doing an IN on a list of literals, snuba will not optimize the query with the HexInt
768
- # column processor which means we won't be taking advantage of the span_id index but if we only do this when we
769
- # have a lot of query_spans we should have a great performance improvement still once we do that we can simplify
770
- # this code and always apply this optimization
771
- span_condition_value = Function (
772
- "splitByChar" , ["," , "," .join (strip_span_id (span_id ) for span_id in query_spans )]
773
- )
774
- else :
775
- span_condition_value = Function ("tuple" , list (query_spans ))
776
- # Building the condition manually, a performance optimization since we might put thousands of span ids
777
- # and this way we skip both parsimonious and the builder
778
- parents_query .add_conditions (
779
- [Condition (Column (parents_query .resolve_column_name ("id" )), Op .IN , span_condition_value )]
780
- )
781
- return parents_query
782
-
783
-
784
728
def pad_span_id (span : str | None ) -> str :
785
729
"""Snuba might return the span id without leading 0s since they're stored as UInt64
786
730
which means a span like 0011 gets converted to an int, then back so we'll get `11` instead"""
@@ -789,171 +733,6 @@ def pad_span_id(span: str | None) -> str:
789
733
return span .rjust (16 , "0" )
790
734
791
735
792
- @sentry_sdk .tracing .trace
793
- def augment_transactions_with_spans (
794
- transactions : Sequence [SnubaTransaction ],
795
- errors : Sequence [SnubaError ],
796
- trace_id : str ,
797
- params : SnubaParams ,
798
- query_source : QuerySource | None = QuerySource .SENTRY_BACKEND ,
799
- ) -> Sequence [SnubaTransaction ]:
800
- """Augment the list of transactions with parent, error and problem data"""
801
- with sentry_sdk .start_span (op = "augment.transactions" , name = "setup" ):
802
- trace_parent_spans = set () # parent span ids of segment spans
803
- transaction_problem_map : dict [str , SnubaTransaction ] = {}
804
- problem_project_map : dict [int , list [str ]] = {}
805
- issue_occurrences = []
806
- occurrence_spans : set [str ] = set ()
807
- error_spans = set ()
808
- projects = set ()
809
- for error in errors :
810
- if "trace.span" in error :
811
- error ["trace.span" ] = pad_span_id (error ["trace.span" ])
812
- error_spans .add (error ["trace.span" ])
813
- projects .add (error ["project.id" ])
814
- ts_params = find_timestamp_params (transactions )
815
- time_buffer = options .get ("performance.traces.span_query_timebuffer_hours" )
816
- set_span_attribute ("trace_view.spans.time_buffer" , time_buffer )
817
- if ts_params ["min" ]:
818
- params .start = ts_params ["min" ] - timedelta (hours = time_buffer )
819
- if ts_params ["max" ]:
820
- params .end = ts_params ["max" ] + timedelta (hours = time_buffer )
821
-
822
- if ts_params ["max" ] and ts_params ["min" ]:
823
- set_span_attribute (
824
- "trace_view.trace_duration" , (ts_params ["max" ] - ts_params ["min" ]).total_seconds ()
825
- )
826
- sentry_sdk .set_tag ("trace_view.missing_timestamp_constraints" , False )
827
- else :
828
- sentry_sdk .set_tag ("trace_view.missing_timestamp_constraints" , True )
829
-
830
- with sentry_sdk .start_span (op = "augment.transactions" , name = "get transaction span ids" ):
831
- for index , transaction in enumerate (transactions ):
832
- transaction ["occurrence_spans" ] = []
833
- transaction ["issue_occurrences" ] = []
834
-
835
- project = transaction ["project.id" ]
836
- projects .add (project )
837
-
838
- # Pull out occurrence data
839
- transaction_problem_map [transaction ["id" ]] = transaction
840
- if project not in problem_project_map :
841
- problem_project_map [project ] = []
842
- if transaction ["occurrence_id" ] is not None :
843
- problem_project_map [project ].extend (transaction ["occurrence_id" ])
844
-
845
- if not transaction ["trace.parent_span" ]:
846
- continue
847
- # parent span ids of the segment spans
848
- trace_parent_spans .add (transaction ["trace.parent_span" ])
849
-
850
- with sentry_sdk .start_span (op = "augment.transactions" , name = "get perf issue span ids" ):
851
- for problem_project , occurrences in problem_project_map .items ():
852
- if occurrences :
853
- issue_occurrences .extend (
854
- [
855
- occurrence
856
- for occurrence in IssueOccurrence .fetch_multi (occurrences , problem_project )
857
- if occurrence is not None
858
- ]
859
- )
860
-
861
- for problem in issue_occurrences :
862
- occurrence_spans = occurrence_spans .union (
863
- set (problem .evidence_data ["offender_span_ids" ])
864
- )
865
-
866
- with sentry_sdk .start_span (op = "augment.transactions" , name = "create query params" ):
867
- unfiltered_spans = {* trace_parent_spans , * error_spans , * occurrence_spans }
868
- query_spans : set [str ] = {
869
- span for span in unfiltered_spans if span is not None and span != ""
870
- }
871
- # If there are no spans to query just return transactions as is
872
- if len (query_spans ) == 0 :
873
- return transactions
874
-
875
- # Fetch parent span ids of segment spans and their corresponding
876
- # transaction id so we can link parent/child transactions in
877
- # a trace.
878
- spans_params = params .copy ()
879
- spans_params .projects = [p for p in params .projects if p .id in projects ]
880
-
881
- # If we're querying over 100 span ids, lets split the query into 3
882
- sentry_sdk .set_tag ("trace_view.use_spans.span_len" , len (query_spans ))
883
-
884
- # Whether any of the span queries hit their query limit, which means that clickhouse would've exited early
885
- # this is for tagging so we can see the performance difference
886
- hit_limit = False
887
- # The max query size according to snuba/clickhouse docs is 256KiB, or about 256 thousand characters
888
- # Each span id maps to being 20 characters; 16 characters turned back into a number maxes out at 20
889
- # which at 10k transaction span ids, and even another 10k error span ids (which would only happen if there's no
890
- # crossover) that's 20k * 20 = 400k bytes, with 3 queries that should be 133,333 bytes which shouldn't be anywhere
891
- # near the 256KiB max even with the other parts of the query.
892
- # Experimentally (running queries in snuba admin) we've found the max query size is actually 131,535 bytes or
893
- # 128.45KiB. Taking that into account, (131,535-10,000(for projects etc))/20 = 6000, means that we can fit at most
894
- # 6000 span ids per query, Adding a bit of a buffer, if the query is for more than 12,500 spans we'll do 4 chunks
895
- # instead of 3
896
- if len (query_spans ) > 100 :
897
- list_spans = list (query_spans )
898
- if len (query_spans ) < 12_500 :
899
- total_chunks = 3
900
- else :
901
- total_chunks = 4
902
- set_span_attribute ("trace_view.span_query.total_chunks" , total_chunks )
903
- chunks = chunked (list_spans , (len (list_spans ) // total_chunks ) + 1 )
904
- queries = [build_span_query (trace_id , spans_params , chunk ) for chunk in chunks ]
905
- results = bulk_snuba_queries (
906
- [query .get_snql_query () for query in queries ],
907
- referrer = Referrer .API_TRACE_VIEW_GET_PARENTS .value ,
908
- query_source = query_source ,
909
- )
910
- parents_results = results [0 ]
911
- for result , query in zip (results , queries ):
912
- if len (result ["data" ]) == query .limit .limit :
913
- hit_limit = True
914
- for result in results [1 :]:
915
- parents_results ["data" ].extend (result ["data" ])
916
- else :
917
- parents_query = build_span_query (trace_id , spans_params , list (query_spans ))
918
- parents_results = parents_query .run_query (
919
- referrer = Referrer .API_TRACE_VIEW_GET_PARENTS .value ,
920
- query_source = query_source ,
921
- )
922
- if len (parents_results ) == parents_query .limit .limit :
923
- hit_limit = True
924
- sentry_sdk .set_tag ("trace_view.span_query.hit_limit" , hit_limit )
925
-
926
- parent_map = {}
927
- if "data" in parents_results :
928
- for parent in parents_results ["data" ]:
929
- parent ["span_id" ] = pad_span_id (parent ["span_id" ])
930
- parent_map [parent ["span_id" ]] = parent
931
-
932
- with sentry_sdk .start_span (op = "augment.transactions" , name = "linking transactions" ):
933
- for transaction in transactions :
934
- # For a given transaction, if parent span id exists in the tranaction (so this is
935
- # not a root span), see if the indexed spans data can tell us what the parent
936
- # transaction id is.
937
- if "trace.parent_span" in transaction :
938
- parent = parent_map .get (transaction ["trace.parent_span" ])
939
- if parent is not None :
940
- transaction ["trace.parent_transaction" ] = parent ["transaction.id" ]
941
- with sentry_sdk .start_span (op = "augment.transactions" , name = "linking perf issues" ):
942
- for problem in issue_occurrences :
943
- for span_id in problem .evidence_data ["offender_span_ids" ]:
944
- parent = parent_map .get (span_id )
945
- if parent is not None :
946
- transaction_problem = transaction_problem_map [problem .event_id ]
947
- occurrence = parent .copy ()
948
- occurrence ["problem" ] = problem
949
- transaction_problem ["occurrence_spans" ].append (occurrence )
950
- with sentry_sdk .start_span (op = "augment.transactions" , name = "linking errors" ):
951
- for error in errors :
952
- parent = parent_map .get (error ["trace.span" ])
953
- error ["trace.transaction" ] = parent ["transaction.id" ] if parent is not None else None
954
- return transactions
955
-
956
-
957
736
class OrganizationEventsTraceEndpointBase (OrganizationEventsV2EndpointBase ):
958
737
publish_status = {
959
738
"GET" : ApiPublishStatus .PRIVATE ,
@@ -1072,13 +851,8 @@ def get(self, request: Request, organization: Organization, trace_id: str) -> Ht
1072
851
1073
852
# Detailed is deprecated now that we want to use spans instead
1074
853
detailed = request .GET .get ("detailed" , "0" ) == "1"
1075
- # Temporary url params until we finish migrating the frontend
1076
- use_spans = request .GET .get ("useSpans" , "0" ) == "1"
1077
854
update_snuba_params_with_timestamp (request , snuba_params )
1078
855
1079
- sentry_sdk .set_tag ("trace_view.using_spans" , str (use_spans ))
1080
- if detailed and use_spans :
1081
- raise ParseError ("Cannot return a detailed response while using spans" )
1082
856
limit = min (int (request .GET .get ("limit" , MAX_TRACE_SIZE )), 10_000 )
1083
857
event_id = (
1084
858
request .GET .get ("targetId" ) or request .GET .get ("event_id" ) or request .GET .get ("eventId" )
@@ -1094,33 +868,14 @@ def get(self, request: Request, organization: Organization, trace_id: str) -> Ht
1094
868
trace_id , snuba_params , query_source = query_source
1095
869
)
1096
870
1097
- if use_spans :
1098
- transactions , errors = query_trace_data (
1099
- trace_id ,
1100
- snuba_params ,
1101
- transaction_params ,
1102
- limit ,
1103
- event_id ,
1104
- use_spans ,
1105
- query_source = query_source ,
1106
- )
1107
- transactions = augment_transactions_with_spans (
1108
- transactions ,
1109
- errors ,
1110
- trace_id ,
1111
- snuba_params ,
1112
- query_source = query_source ,
1113
- )
1114
- else :
1115
- transactions , errors = query_trace_data (
1116
- trace_id ,
1117
- snuba_params ,
1118
- transaction_params ,
1119
- limit ,
1120
- None ,
1121
- False ,
1122
- query_source = query_source ,
1123
- )
871
+ transactions , errors = query_trace_data (
872
+ trace_id ,
873
+ snuba_params ,
874
+ transaction_params ,
875
+ limit ,
876
+ None ,
877
+ query_source = query_source ,
878
+ )
1124
879
self .record_analytics (transactions , trace_id , request .user .id , organization .id )
1125
880
1126
881
warning_extra : dict [str , str ] = {"trace" : trace_id , "organization" : organization .slug }
@@ -1150,7 +905,6 @@ def get(self, request: Request, organization: Organization, trace_id: str) -> Ht
1150
905
warning_extra ,
1151
906
event_id ,
1152
907
detailed ,
1153
- use_spans ,
1154
908
query_source = self .get_request_source (request ),
1155
909
)
1156
910
)
@@ -1165,7 +919,6 @@ def serialize(
1165
919
warning_extra : dict [str , str ],
1166
920
event_id : str | None ,
1167
921
detailed : bool = False ,
1168
- use_spans : bool = False ,
1169
922
query_source : QuerySource | None = None ,
1170
923
) -> Any :
1171
924
raise NotImplementedError
@@ -1238,12 +991,9 @@ def serialize(
1238
991
warning_extra : dict [str , str ],
1239
992
event_id : str | None ,
1240
993
detailed : bool = False ,
1241
- use_spans : bool = False ,
1242
994
query_source : QuerySource | None = None ,
1243
995
) -> dict [str , list [LightResponse | TraceError ]]:
1244
996
"""Because the light endpoint could potentially have gaps between root and event we return a flattened list"""
1245
- if use_spans :
1246
- raise ParseError (detail = "useSpans isn't supported on the trace-light" )
1247
997
if event_id is None :
1248
998
raise ParseError (detail = "An event_id is required for the light trace" )
1249
999
snuba_event , nodestore_event = self .get_current_transaction (transactions , errors , event_id )
@@ -1413,27 +1163,13 @@ def serialize(
1413
1163
warning_extra : dict [str , str ],
1414
1164
event_id : str | None ,
1415
1165
detailed : bool = False ,
1416
- use_spans : bool = False ,
1417
1166
query_source : QuerySource | None = None ,
1418
1167
) -> SerializedTrace :
1419
1168
"""For the full event trace, we return the results as a graph instead of a flattened list
1420
1169
1421
1170
if event_id is passed, we prune any potential branches of the trace to make as few nodestore calls as
1422
1171
possible
1423
1172
"""
1424
- if use_spans :
1425
- results = self .serialize_with_spans (
1426
- limit ,
1427
- transactions ,
1428
- errors ,
1429
- roots ,
1430
- warning_extra ,
1431
- event_id ,
1432
- detailed ,
1433
- query_source = query_source ,
1434
- )
1435
- return results
1436
-
1437
1173
# Code past here is deprecated, but must continue to exist until sentry installs in every possible environment
1438
1174
# are storing span data, since that's the only way serialize_with_spans will work
1439
1175
event_id_to_nodestore_event = self .nodestore_event_map (transactions )
0 commit comments