Skip to content

Commit b8a5190

Browse files
committed
test(dsc): Improve dsc normalization tests
1 parent 90e78d4 commit b8a5190

3 files changed

Lines changed: 294 additions & 142 deletions

File tree

tests/integration/test_dynamic_sampling.py

Lines changed: 294 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from datetime import datetime
1+
from datetime import datetime, timezone
2+
from typing import Literal
23
import uuid
34
import json
45

@@ -7,6 +8,7 @@
78
import pytest
89
from sentry_sdk.envelope import Envelope, Item, PayloadRef
910
import queue
11+
from .asserts import time_within_delta
1012

1113

1214
def _create_transaction_item(trace_id=None, event_id=None, transaction=None, **kwargs):
@@ -858,3 +860,294 @@ def test_invalid_global_generic_filters_skip_dynamic_sampling(mini_sentry, relay
858860

859861
relay.send_envelope(project_id, envelope)
860862
assert mini_sentry.get_captured_envelope()
863+
864+
865+
def get_transaction_envelope(
866+
trace_id: str,
867+
segment_id: str,
868+
child_id_1: str,
869+
child_id_2: str,
870+
dsc: Literal["dsc_with_tx", "dsc_no_tx", "no_dsc"],
871+
sampling_project_config: dict,
872+
):
873+
ts = datetime.now(timezone.utc)
874+
875+
# Create transaction
876+
event = {
877+
"type": "transaction",
878+
"timestamp": ts.isoformat(),
879+
"start_timestamp": ts.timestamp(),
880+
"spans": [],
881+
"contexts": {
882+
"trace": {
883+
"op": "/trace/",
884+
"trace_id": trace_id,
885+
"span_id": segment_id,
886+
}
887+
},
888+
"transaction": "/event/",
889+
}
890+
891+
# Add child spans
892+
event["spans"] = [
893+
{
894+
"trace_id": trace_id,
895+
"span_id": child_id_1,
896+
"parent_span_id": segment_id,
897+
"start_timestamp": ts.timestamp(),
898+
"timestamp": ts.timestamp() + 0.3,
899+
},
900+
{
901+
"trace_id": trace_id,
902+
"span_id": child_id_2,
903+
"parent_span_id": segment_id,
904+
"start_timestamp": ts.timestamp(),
905+
"timestamp": ts.timestamp() + 0.3,
906+
"data": {
907+
"sentry.dsc.trace_id": trace_id,
908+
"sentry.dsc.transaction": "/spandata/",
909+
"sentry.dsc.project_id": "41",
910+
},
911+
},
912+
]
913+
914+
# Add transaction to envelope
915+
envelope = Envelope()
916+
envelope.add_item(Item(payload=PayloadRef(json=event), type="transaction"))
917+
918+
# Add DSC to envelope
919+
if dsc != "no_dsc":
920+
envelope.headers["trace"] = {
921+
"trace_id": trace_id,
922+
"public_key": sampling_project_config["publicKeys"][0]["publicKey"],
923+
"sample_rate": "1",
924+
"sample_rand": "0.9",
925+
"sampled": "true",
926+
"release": "some_release",
927+
"environment": "some_environment",
928+
**({"transaction": "/dsc/"} if dsc == "dsc_with_tx" else {}),
929+
"org_id": sampling_project_config["organizationId"],
930+
}
931+
932+
return envelope
933+
934+
935+
def get_v2_envelope(
936+
trace_id: str,
937+
segment_id: str,
938+
child_id_1: str,
939+
child_id_2: str,
940+
dsc: Literal["dsc_with_tx", "dsc_no_tx", "no_dsc"],
941+
sampling_project_config: dict,
942+
):
943+
ts = datetime.now(timezone.utc)
944+
945+
spans = [
946+
# Segment span.
947+
{
948+
"start_timestamp": ts.timestamp(),
949+
"end_timestamp": ts.timestamp() + 0.5,
950+
"trace_id": trace_id,
951+
"span_id": segment_id,
952+
"is_segment": True,
953+
"name": "root",
954+
"status": "ok",
955+
},
956+
# Child span.
957+
{
958+
"start_timestamp": ts.timestamp(),
959+
"end_timestamp": ts.timestamp() + 0.3,
960+
"trace_id": trace_id,
961+
"span_id": child_id_1,
962+
"parent_span_id": segment_id,
963+
"is_segment": False,
964+
"name": "child1",
965+
"status": "ok",
966+
},
967+
# Child span which already has `sentry.dsc.*` attributes set.
968+
{
969+
"start_timestamp": ts.timestamp(),
970+
"end_timestamp": ts.timestamp() + 0.3,
971+
"trace_id": trace_id,
972+
"span_id": child_id_2,
973+
"parent_span_id": segment_id,
974+
"is_segment": False,
975+
"name": "child2",
976+
"status": "ok",
977+
"attributes": {
978+
"sentry.dsc.trace_id": {"type": "string", "value": trace_id},
979+
"sentry.dsc.transaction": {"type": "string", "value": "/spandata/"},
980+
"sentry.dsc.project_id": {"type": "string", "value": "41"},
981+
},
982+
},
983+
]
984+
985+
# Add spans to envelope
986+
envelope = Envelope()
987+
envelope.add_item(
988+
Item(
989+
type="span",
990+
payload=PayloadRef(json={"items": spans}),
991+
content_type="application/vnd.sentry.items.span.v2+json",
992+
headers={"item_count": len(spans)},
993+
)
994+
)
995+
996+
# Add DSC to envelope
997+
trace_info = (
998+
{
999+
"trace_id": trace_id,
1000+
"public_key": sampling_project_config["publicKeys"][0]["publicKey"],
1001+
"sample_rate": "1",
1002+
"sampled": "true",
1003+
"release": "some_release",
1004+
"environment": "some_environment",
1005+
**({"transaction": "/dsc/"} if dsc == "dsc_with_tx" else {}),
1006+
}
1007+
if dsc != "no_dsc"
1008+
else None
1009+
)
1010+
envelope.headers["trace"] = trace_info
1011+
1012+
return envelope
1013+
1014+
1015+
@pytest.mark.parametrize("span_type", ["tx", "v2"])
1016+
@pytest.mark.parametrize("org", ["same_org", "diff_org"])
1017+
@pytest.mark.parametrize("dsc", ["dsc_with_tx", "dsc_no_tx", "no_dsc"])
1018+
def test_dsc_normalization(
1019+
mini_sentry,
1020+
relay,
1021+
relay_with_processing,
1022+
spans_consumer,
1023+
metrics_consumer,
1024+
dsc,
1025+
org,
1026+
span_type,
1027+
):
1028+
segment_id = "a" * 16
1029+
child_id_1 = "b" * 16
1030+
child_id_2 = "c" * 16
1031+
trace_id = "a0fa8803753e40fd8124b21eeb2986b5"
1032+
project_id = 42
1033+
sampling_project_id = 43
1034+
org_id = 1
1035+
sampling_org_id = 1 if org == "same_org" else 2
1036+
mini_sentry.add_full_project_config(project_id, extra={"organizationId": org_id})
1037+
sampling_project_config = mini_sentry.add_full_project_config(
1038+
sampling_project_id, extra={"organizationId": sampling_org_id}
1039+
)
1040+
relay = relay(relay_with_processing())
1041+
spans_consumer = spans_consumer()
1042+
metrics_consumer = metrics_consumer()
1043+
# Expected results based on the parameters
1044+
expected_tx, expected_project_id, expected_root_org_id = {
1045+
# DSC with tx + same org
1046+
("dsc_with_tx", "same_org", "tx"): ("/dsc/", sampling_project_id, org_id),
1047+
("dsc_with_tx", "same_org", "v2"): ("/dsc/", sampling_project_id, org_id),
1048+
# ----------------------------------------------------------------------
1049+
# DSC without tx + same org
1050+
("dsc_no_tx", "same_org", "tx"): (None, sampling_project_id, org_id),
1051+
("dsc_no_tx", "same_org", "v2"): (None, sampling_project_id, org_id),
1052+
# ----------------------------------------------------------------------
1053+
# DSC with tx + different org
1054+
("dsc_with_tx", "diff_org", "tx"): ("/event/", project_id, org_id),
1055+
("dsc_with_tx", "diff_org", "v2"): ("/dsc/", project_id, org_id),
1056+
# ----------------------------------------------------------------------
1057+
# DSC without tx + different org
1058+
("dsc_no_tx", "diff_org", "tx"): ("/event/", project_id, org_id),
1059+
("dsc_no_tx", "diff_org", "v2"): (None, project_id, org_id),
1060+
# ----------------------------------------------------------------------
1061+
# No DSC
1062+
("no_dsc", "same_org", "tx"): ("/event/", project_id, org_id),
1063+
("no_dsc", "diff_org", "tx"): ("/event/", project_id, org_id),
1064+
("no_dsc", "same_org", "v2"): (None, None, None), # rejected, see early return
1065+
("no_dsc", "diff_org", "v2"): (None, None, None), # rejected, see early return
1066+
# ----------------------------------------------------------------------
1067+
}[dsc, org, span_type]
1068+
1069+
envelope = (
1070+
get_transaction_envelope(
1071+
trace_id=trace_id,
1072+
segment_id=segment_id,
1073+
child_id_1=child_id_1,
1074+
child_id_2=child_id_2,
1075+
dsc=dsc,
1076+
sampling_project_config=sampling_project_config,
1077+
)
1078+
if span_type == "tx"
1079+
else get_v2_envelope(
1080+
trace_id=trace_id,
1081+
segment_id=segment_id,
1082+
child_id_1=child_id_1,
1083+
child_id_2=child_id_2,
1084+
dsc=dsc,
1085+
sampling_project_config=sampling_project_config,
1086+
)
1087+
)
1088+
1089+
relay.send_envelope(project_id, envelope)
1090+
metrics = metrics_consumer.get_metrics(with_headers=False)
1091+
metrics = [m for m in metrics if "count_per_root_project" in m["name"]]
1092+
spans = {s["span_id"]: s for s in spans_consumer.get_spans()}
1093+
1094+
if dsc == "no_dsc" and span_type == "v2":
1095+
assert len(spans) == 0
1096+
assert len(metrics) == 0
1097+
return
1098+
1099+
def get_dsc_attr(attr: str, span_id: str):
1100+
return spans[span_id]["attributes"].get(f"sentry.dsc.{attr}", {}).get("value")
1101+
1102+
# Segment span
1103+
assert spans[segment_id]["is_segment"] is True
1104+
assert get_dsc_attr("transaction", segment_id) == expected_tx
1105+
assert get_dsc_attr("project_id", segment_id) == str(expected_project_id)
1106+
assert get_dsc_attr("trace_id", segment_id) == trace_id
1107+
1108+
# Child span
1109+
assert spans[child_id_1]["is_segment"] is False
1110+
assert get_dsc_attr("transaction", child_id_1) == expected_tx
1111+
assert get_dsc_attr("project_id", child_id_1) == str(expected_project_id)
1112+
assert get_dsc_attr("trace_id", child_id_1) == trace_id
1113+
1114+
# Child span with sentry.dsc.* attributes already set in its span data
1115+
assert spans[child_id_2]["is_segment"] is False
1116+
assert get_dsc_attr("transaction", child_id_2) == "/spandata/"
1117+
assert get_dsc_attr("project_id", child_id_2) == "41"
1118+
assert get_dsc_attr("trace_id", child_id_2) == trace_id
1119+
1120+
assert metrics == [
1121+
{
1122+
"name": "c:spans/count_per_root_project@none",
1123+
"org_id": expected_root_org_id,
1124+
"project_id": expected_project_id,
1125+
"received_at": time_within_delta(),
1126+
"retention_days": 90,
1127+
"tags": {
1128+
"decision": "keep",
1129+
"is_segment": "false",
1130+
"target_project_id": str(project_id),
1131+
**({"transaction": expected_tx} if expected_tx else {}),
1132+
},
1133+
"timestamp": time_within_delta(),
1134+
"type": "c",
1135+
"value": 2.0,
1136+
},
1137+
{
1138+
"name": "c:spans/count_per_root_project@none",
1139+
"org_id": expected_root_org_id,
1140+
"project_id": expected_project_id,
1141+
"received_at": time_within_delta(),
1142+
"retention_days": 90,
1143+
"tags": {
1144+
"decision": "keep",
1145+
"is_segment": "true",
1146+
"target_project_id": str(project_id),
1147+
**({"transaction": expected_tx} if expected_tx else {}),
1148+
},
1149+
"timestamp": time_within_delta(),
1150+
"type": "c",
1151+
"value": 1.0,
1152+
},
1153+
]

tests/integration/test_spans.py

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,62 +1248,3 @@ def test_outcomes_for_trimmed_spans(mini_sentry, relay):
12481248
"timestamp": any(),
12491249
},
12501250
]
1251-
1252-
1253-
def test_spans_dsc_normalization(
1254-
mini_sentry, relay, relay_with_processing, spans_consumer
1255-
):
1256-
project_id = 42
1257-
mini_sentry.add_full_project_config(project_id)
1258-
relay = relay(relay_with_processing())
1259-
spans_consumer = spans_consumer()
1260-
ts = datetime.now(timezone.utc)
1261-
event = make_transaction({"event_id": "cbf6960622e14a45abc1f03b2055b186"})
1262-
# Large data for testing if DSC attributes are trimmed from trace context
1263-
event["contexts"]["trace"]["data"] = {"big_content": "1" * 10000}
1264-
event["spans"] = [
1265-
{
1266-
"trace_id": "a0fa8803753e40fd8124b21eeb2986b5",
1267-
"span_id": "bbbbbbbbbbbbbbbb",
1268-
"parent_span_id": "968cff94913ebb07",
1269-
"start_timestamp": ts.timestamp(),
1270-
"timestamp": ts.timestamp() + 0.3,
1271-
},
1272-
{
1273-
"trace_id": "a0fa8803753e40fd8124b21eeb2986b5",
1274-
"span_id": "cccccccccccccccc",
1275-
"parent_span_id": "968cff94913ebb07",
1276-
"start_timestamp": ts.timestamp(),
1277-
"timestamp": ts.timestamp() + 0.3,
1278-
"data": {
1279-
"sentry.dsc.trace_id": "a0fa8803753e40fd8124b21eeb2986b5",
1280-
"sentry.dsc.transaction": "/transaction/already/exists",
1281-
"sentry.dsc.project_id": "41",
1282-
},
1283-
},
1284-
]
1285-
1286-
relay.send_event(project_id, event)
1287-
spans = {s["span_id"]: s for s in spans_consumer.get_spans()}
1288-
1289-
def get_transaction(span_id: str):
1290-
return spans[span_id]["attributes"]["sentry.dsc.transaction"]["value"]
1291-
1292-
def get_project_id(span_id: str):
1293-
return spans[span_id]["attributes"]["sentry.dsc.project_id"]["value"]
1294-
1295-
def get_trace_id(span_id: str):
1296-
return spans[span_id]["attributes"]["sentry.dsc.trace_id"]["value"]
1297-
1298-
assert spans["968cff94913ebb07"]["is_segment"] is True
1299-
assert spans["bbbbbbbbbbbbbbbb"]["is_segment"] is False
1300-
assert spans["cccccccccccccccc"]["is_segment"] is False
1301-
assert get_transaction("968cff94913ebb07") == "hi"
1302-
assert get_transaction("bbbbbbbbbbbbbbbb") == "hi"
1303-
assert get_transaction("cccccccccccccccc") == "/transaction/already/exists"
1304-
assert get_project_id("968cff94913ebb07") == "42"
1305-
assert get_project_id("bbbbbbbbbbbbbbbb") == "42"
1306-
assert get_project_id("cccccccccccccccc") == "41"
1307-
assert get_trace_id("968cff94913ebb07") == "a0fa8803753e40fd8124b21eeb2986b5"
1308-
assert get_trace_id("bbbbbbbbbbbbbbbb") == "a0fa8803753e40fd8124b21eeb2986b5"
1309-
assert get_trace_id("cccccccccccccccc") == "a0fa8803753e40fd8124b21eeb2986b5"

0 commit comments

Comments
 (0)