Skip to content
Draft
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
22 changes: 17 additions & 5 deletions manifests/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1651,7 +1651,7 @@ manifest:
? tests/parametric/test_otel_span_methods.py::Test_Otel_Span_Methods::test_otel_span_extended_reserved_attributes_overrides_analytics_event
: - declaration: missing_feature (Implemented in 0.96.0)
component_version: <=0.95.0
tests/parametric/test_otel_span_methods.py::Test_Otel_Span_Methods::test_otel_span_link_attribute_handling: missing_feature (Not implemented, does not break out arrays into dot notation)
tests/parametric/test_otel_span_methods.py::Test_Otel_Span_Methods::test_otel_span_link_attribute_handling: v1.26.0-dev
tests/parametric/test_otel_span_methods.py::Test_Otel_Span_Methods::test_otel_span_started_with_link_from_another_span:
- declaration: missing_feature (Implemented in 0.97.0)
component_version: <0.97.0
Expand Down Expand Up @@ -1713,9 +1713,20 @@ manifest:
tests/parametric/test_sampling_span_tags.py::Test_Sampling_Span_Tags::test_tags_defaults_rate_1_and_rate_limit_3_sst010: bug (APMAPI-737)
tests/parametric/test_sampling_span_tags.py::Test_Sampling_Span_Tags::test_tags_defaults_rate_1_and_rule_0_sst006: bug (APMAPI-737)
tests/parametric/test_sampling_span_tags.py::Test_Sampling_Span_Tags::test_tags_defaults_rate_tiny_sst004: bug (APMAPI-737)
tests/parametric/test_span_events.py: missing_feature
tests/parametric/test_span_events.py::Test_Span_Events::test_span_with_event_v05: '>=1.16.0'
tests/parametric/test_span_links.py: missing_feature
tests/parametric/test_sampling_span_tags.py::Test_Sampling_Span_Tags::test_tags_defaults_sst002:
- declaration: bug (APMAPI-1545)
component_version: '>=1.26.0-dev'
tests/parametric/test_span_events.py::Test_Span_Events::test_span_with_event_v04: v1.26.0-dev
tests/parametric/test_span_events.py::Test_Span_Events::test_span_with_event_v05: missing_feature
tests/parametric/test_span_events.py::Test_Span_Events::test_span_with_event_v07: v1.26.0-dev
tests/parametric/test_span_events.py::Test_Span_Events::test_span_with_invalid_event_attributes: missing_feature
tests/parametric/test_span_links.py::Test_Span_Links::test_span_link_from_distributed_datadog_headers: missing_feature
tests/parametric/test_span_links.py::Test_Span_Links::test_span_link_from_distributed_w3c_headers: missing_feature
tests/parametric/test_span_links.py::Test_Span_Links::test_span_link_propagated_sampling_decisions: missing_feature
tests/parametric/test_span_links.py::Test_Span_Links::test_span_started_with_link_v04: v1.26.0-dev
tests/parametric/test_span_links.py::Test_Span_Links::test_span_started_with_link_v05: missing_feature
tests/parametric/test_span_links.py::Test_Span_Links::test_span_started_with_link_v05_span_id_padding: missing_feature
tests/parametric/test_span_links.py::Test_Span_Links::test_span_with_attached_links: v1.26.0-dev
? tests/parametric/test_span_sampling.py::Test_Span_Sampling::test_child_span_selected_and_root_dropped_by_sss_when_dropping_policy_is_active017
: missing_feature (The PHP tracer always sends the full trace to the agent.)
tests/parametric/test_span_sampling.py::Test_Span_Sampling::test_entire_trace_dropped_when_dropping_policy_is_active018: missing_feature (The PHP tracer always sends the full trace to the agent.)
Expand Down Expand Up @@ -2469,4 +2480,5 @@ manifest:
tests/test_telemetry.py::Test_TelemetrySCAEnvVar: v0.99.1
tests/test_telemetry.py::Test_TelemetryV2: v0.90
tests/test_telemetry.py::Test_TelemetryV2::test_app_started_product_info: missing_feature (Product started missing (both in libdatadog and php))
tests/test_v1_payloads.py: missing_feature
tests/test_v1_payloads.py: v1.26.0-dev
tests/test_v1_payloads.py::Test_V1SpanLinks: missing_feature (no span link is created from conflicting datadog and tracecontext contexts, see Test_Span_Links_From_Conflicting_Contexts)
2 changes: 1 addition & 1 deletion tests/appsec/api_security/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def assert_llm_span(request: HttpResponse, model: str) -> None:
span = interfaces.library.get_root_span(request)
assert span["meta"]["appsec.events.llm.call.provider"] == "openai"
assert span["meta"]["appsec.events.llm.call.model"] == model
assert span["metrics"]["_sampling_priority_v1"] == 2
assert span.get_sampling_priority() == 2


@rfc(
Expand Down
2 changes: 1 addition & 1 deletion tests/appsec/test_automated_payment_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def _validator(span: DataDogLibrarySpan):
return False

assert span["meta"]["appsec.events.payments.integration"] == "stripe"
assert span["metrics"]["_sampling_priority_v1"] == 2
assert span.get_sampling_priority() == 2

return validator(span)

Expand Down
12 changes: 8 additions & 4 deletions tests/appsec/test_trace_tagging.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ def validate(span: DataDogLibrarySpan):

assert span["meta"]["_dd.appsec.trace.agent"].startswith("TraceTagging/v1")
assert span["metrics"]["_dd.appsec.trace.integer"] == 662607015
assert span["metrics"].get("_sampling_priority_v1") < SamplingPriority.USER_KEEP
sampling_priority = span.get_sampling_priority()
assert sampling_priority is not None
assert sampling_priority < SamplingPriority.USER_KEEP

return True

Expand All @@ -55,7 +57,7 @@ def validate(span: DataDogLibrarySpan):

assert span["meta"]["_dd.appsec.trace.agent"].startswith("TraceTagging/v2")
assert span["metrics"]["_dd.appsec.trace.integer"] == 602214076
assert span["metrics"].get("_sampling_priority_v1") == SamplingPriority.USER_KEEP
assert span.get_sampling_priority() == SamplingPriority.USER_KEEP

return True

Expand All @@ -77,7 +79,7 @@ def validate(span: DataDogLibrarySpan):

assert span["meta"]["_dd.appsec.trace.agent"].startswith("TraceTagging/v3")
assert span["metrics"]["_dd.appsec.trace.integer"] == 299792458
assert span["metrics"].get("_sampling_priority_v1") == SamplingPriority.USER_KEEP
assert span.get_sampling_priority() == SamplingPriority.USER_KEEP

return True

Expand All @@ -100,7 +102,9 @@ def validate(span: DataDogLibrarySpan):

assert span["meta"]["_dd.appsec.trace.agent"].startswith("TraceTagging/v4")
assert span["metrics"]["_dd.appsec.trace.integer"] == 1729
assert span["metrics"].get("_sampling_priority_v1") < SamplingPriority.USER_KEEP
sampling_priority = span.get_sampling_priority()
assert sampling_priority is not None
assert sampling_priority < SamplingPriority.USER_KEEP

return True

Expand Down
6 changes: 4 additions & 2 deletions tests/integrations/test_db_integrations_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ def test_db_name(self):
def test_span_kind(self, excluded_operations: tuple[str, ...] = ()):
"""Describes the relationship between the Span, its parents, and its children in a Trace."""

for _, span_meta in self.get_spans_meta(excluded_operations):
assert span_meta["span.kind"] == "client"
for _, lib_span, agent_span in self.get_spans(excluded_operations):
assert lib_span.meta["span.kind"] == "client"
# in the efficient trace payload format, the agent reports the kind as a top-level enum field
assert agent_span.get_span_kind() in ("client", "SPAN_KIND_CLIENT")

def test_runtime_id(self):
"""Unique identifier for the current process."""
Expand Down
4 changes: 3 additions & 1 deletion tests/parametric/test_otel_api_interoperability.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from opentelemetry.trace import SpanKind
from utils.docker_fixtures.spec.trace import find_trace, find_span, retrieve_span_links, find_only_span, find_root_span
from utils.docker_fixtures import TestAgentAPI, ParametricTestClientApi as APMLibrary
from utils.dd_types import is_same_boolean


# this global mark applies to all tests in this file.
Expand Down Expand Up @@ -447,7 +448,8 @@ def test_set_attribute_from_otel(self, test_agent: TestAgentAPI, test_library: A
assert root is not None
assert root["metrics"]["int"] == 1
assert root["metrics"]["float"] == 1.0
assert root["meta"]["bool"] == "true"
# With v1.0, tracers can send typed booleans, which dd-apm-test-agent converts to numeric 1/0.
assert is_same_boolean(actual={**root["meta"], **root["metrics"]}.get("bool"), expected="true")
assert root["meta"]["str"] == "string"
assert "none" not in root["meta"]
assert root["meta"]["str_array.0"] == "a"
Expand Down
11 changes: 2 additions & 9 deletions tests/parametric/test_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,7 @@ def test_tracer_repository_url_environment_variable(
assert len(trace) == 2

first_span = find_first_span_in_trace_payload(trace)
# the repository url should be injected ONLY in the first span of the trace
spans_with_git = [span for span in trace if span.get("meta", {}).get("_dd.git.repository_url")]
assert len(spans_with_git) == 1
assert first_span == spans_with_git[0]
# v1 sets git tags at the payload level, so other spans may carry them too
assert first_span["meta"]["_dd.git.repository_url"] == library_env["DD_GIT_REPOSITORY_URL"]

@parametrize("library_env", [{"DD_GIT_COMMIT_SHA": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}])
Expand All @@ -95,11 +92,7 @@ def test_tracer_commit_sha_environment_variable(
assert len(trace) == 2

first_span = find_first_span_in_trace_payload(trace)
# the repository url should be injected ONLY in the first span of the trace
spans_with_git = [span for span in trace if span.get("meta", {}).get("_dd.git.commit.sha")]
assert len(spans_with_git) == 1
assert first_span == spans_with_git[0]

# v1 sets git tags at the payload level, so other spans may carry them too
assert first_span["meta"]["_dd.git.commit.sha"] == library_env["DD_GIT_COMMIT_SHA"]

@parametrize(
Expand Down
17 changes: 12 additions & 5 deletions tests/test_data_integrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_trace_header_diagnostic_check(self):

def validator(data: dict):
request_headers = {h[0].lower() for h in data["request"]["headers"]}
if "x-datadog-diagnostic-check" in request_headers and len(data["request"]["content"]) != 0:
if "x-datadog-diagnostic-check" in request_headers and _trace_count(data) != 0:
raise ValueError("Tracer sent a dignostic request with traces in it")

interfaces.library.validate_all_traces(validator=validator, allow_no_trace=True)
Expand All @@ -64,7 +64,7 @@ def validator(data: dict):
except ValueError as e:
raise ValueError(f"'x-datadog-trace-count' request header is not an integer: {value}") from e

if trace_count != len(data["request"]["content"]):
if trace_count != _trace_count(data):
raise ValueError("x-datadog-trace-count request header didn't match the number of traces")

interfaces.library.validate_all_traces(validator=validator, allow_no_trace=True)
Expand Down Expand Up @@ -234,7 +234,7 @@ def get_span_with_sampling_data(trace: DataDogLibraryTrace):
# sampling priority tag will be returned. This is the same logic found on the trace-agent.
span_with_sampling_data = None
for span in trace:
if span.get("metrics", {}).get("_sampling_priority_v1", None) is not None:
if span.get_sampling_priority() is not None:
if span.get("parent_id") in (0, None):
return span
elif span_with_sampling_data is None:
Expand All @@ -250,8 +250,7 @@ def get_span_with_sampling_data(trace: DataDogLibraryTrace):
if not span:
continue

metrics = span["metrics"]
sampling_priority = metrics.get("_sampling_priority_v1")
sampling_priority = span.get_sampling_priority()
if sampling_priority in (SamplingPriority.AUTO_KEEP, SamplingPriority.USER_KEEP):
trace_ids_reported_by_tracer.add(trace.trace_id_as_int)
if trace.trace_id_as_int not in trace_ids_reported_by_agent:
Expand All @@ -266,5 +265,13 @@ def get_span_with_sampling_data(trace: DataDogLibraryTrace):
raise ValueError("Some traces have not been reported by the agent. See logs for more details")


def _trace_count(data: dict) -> int:
"""Number of traces in a /traces request: v1.0 payloads carry them as chunks"""
content = data["request"]["content"]
if data["path"] == "/v1.0/traces":
return len(content.get("chunks") or [])
return len(content)


def _empty_request(data: dict):
return "content" not in data["request"] or not data["request"]["content"]
28 changes: 9 additions & 19 deletions tests/test_sampling_rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,6 @@ def trace_should_be_kept(sampling_rate: float, trace_id: int):
return ((trace_id * SAMPLING_KNUTH_FACTOR) % SAMPLING_MODULO) <= (sampling_rate * MAX_UINT64)


def _spans_with_parent(traces: list, parent_ids: list):
if not isinstance(traces, list):
logger.error("Traces should be an array")
yield from [] # do not fail here, it's schema's job
else:
for trace in traces:
for span in trace:
if span.get("parent_id") in parent_ids:
yield span


def generate_request_id() -> Generator[int, Any, Any]:
i = 0
while True:
Expand Down Expand Up @@ -260,13 +249,16 @@ def test_sampling_determinism(self):
traces = {trace["parent_id"]: trace for trace in self.traces_determinism}
sampling_decisions_per_trace_id = defaultdict(list)

def validator(data: dict):
for span in _spans_with_parent(data["request"]["content"], list(traces.keys())):
expected_trace_id = traces[(span["parent_id"])]["trace_id"]
sampling_priority = span["metrics"].get("_sampling_priority_v1")
sampling_decisions_per_trace_id[span["trace_id"]].append(sampling_priority)
for data, trace in interfaces.library.get_traces():
for span in trace:
if span.get("parent_id") not in traces:
continue

expected_trace_id = traces[span["parent_id"]]["trace_id"]
sampling_priority = span.get_sampling_priority()
sampling_decisions_per_trace_id[expected_trace_id].append(sampling_priority)

assert span["trace_id"] == expected_trace_id, (
assert span.trace_id_equals(expected_trace_id), (
f"Message: {data['log_filename']}: If parent_id matches, "
f"trace_id should match too expected trace_id {expected_trace_id} "
f"span trace_id : {span['trace_id']}, span parent_id : {span['parent_id']}",
Expand All @@ -276,8 +268,6 @@ def validator(data: dict):
f"Message: {data['log_filename']}: sampling priority should be set"
)

interfaces.library.validate_all(validator, path_filters=["/v0.4/traces", "/v0.5/traces"], allow_no_data=True)

for trace_id, decisions in sampling_decisions_per_trace_id.items():
if len(decisions) < 2:
continue
Expand Down
1 change: 1 addition & 0 deletions utils/build/docker/php/common/rewrite-rules.conf
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ RewriteRule "^/trace/sql$" "/trace_sql/"
RewriteRule "^/trace/mongo$" "/trace_mongo/"
RewriteRule "^/e2e_otel_span$" "/e2e_otel_span/"
RewriteRule "^/e2e_single_span$" "/e2e_single_span/"
RewriteRule "^/add_event$" "/add_event/"
RewriteRule "^/crashme$" "/crashme/"
RewriteRule "^/ffe$" "/ffe/"
RewriteRule "^/exceptionreplay/(.*)$" "/debugger/exceptionreplay/$1" [QSA]
Expand Down
11 changes: 11 additions & 0 deletions utils/build/docker/php/weblogs/laravel11x/routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@
return response('', $code);
});

Route::get('/add_event', function () {
$span = \DDTrace\root_span();
if ($span === null) {
return response('root span not found', 500);
}

$span->events[] = new \DDTrace\SpanEvent('span.event', ['string' => 'value', 'int' => 1]);

return response('[Event added]', 200);
});

Route::get('/trace/manual_keep_drop', function (Request $request) {
$decision = $request->query('decision', '');
if ($decision !== 'keep' && $decision !== 'drop') {
Expand Down
13 changes: 13 additions & 0 deletions utils/build/docker/php/weblogs/plain/add_event.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

// Adds a span event to the request root span (tests/test_span_events.py, tests/test_v1_payloads.py::Test_V1SpanEvents)
$span = \DDTrace\root_span();
if ($span === null) {
http_response_code(500);
echo 'root span not found';
exit;
}

$span->events[] = new \DDTrace\SpanEvent('span.event', ['string' => 'value', 'int' => 1]);

echo '[Event added]';
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ public function status(Request $request): Response
return new Response('', $code);
}

#[Route('/add_event', name: 'add_event', methods: ['GET'])]
public function addEvent(): Response
{
$span = \DDTrace\root_span();
if ($span === null) {
return new Response('root span not found', 500);
}

$span->events[] = new \DDTrace\SpanEvent('span.event', ['string' => 'value', 'int' => 1]);

return new Response('[Event added]', 200);
}

#[Route('/trace/manual_keep_drop', name: 'trace_manual_keep_drop', methods: ['GET'])]
public function traceManualKeepDrop(Request $request): Response
{
Expand Down
2 changes: 1 addition & 1 deletion utils/dd_types/_datadog_library_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def metrics(self) -> dict[str, Any]:
return self.raw_span.get("metrics", {})

def get_sampling_priority(self) -> int | None:
return self["metrics"].get("_sampling_priority_v1")
return self.metrics.get("_sampling_priority_v1")

def get_span_links(self) -> list[DataDogSpanLink]:
if "span_links" in self.raw_span:
Expand Down
2 changes: 1 addition & 1 deletion utils/interfaces/_library/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ def add_appsec_reported_header(self, request: HttpResponse, header_name: str):
self.validate_one_appsec(request, validator=validator.validate, legacy_validator=validator.validate_legacy)

def validate_all_traces(self, validator: Callable[[dict], None], *, allow_no_trace: bool = False):
self.validate_all(validator=validator, allow_no_data=allow_no_trace, path_filters=r"/v0\.[1-9]+/traces")
self.validate_all(validator=validator, allow_no_data=allow_no_trace, path_filters=r"/v[0-9]+\.[0-9]+/traces")

def validate_one_trace(self, request: HttpResponse, validator: Callable[[DataDogLibraryTrace], bool]):
"""Will call validator() on all traces trigerred by request. validator() returns a boolean :
Expand Down
Loading