Skip to content

Commit

Permalink
Merge branch '2.17' into juanjux/new-testrunner-for-flaky-tests-217
Browse files Browse the repository at this point in the history
  • Loading branch information
erikayasuda authored Jan 15, 2025
2 parents bfda6c4 + 3961c8d commit edb7a1c
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 116 deletions.
1 change: 1 addition & 0 deletions .github/workflows/django-overhead-profile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
DD_PROFILING_ENABLED: "1"
DD_PROFILING_STACK_V2_ENABLED: ${{ matrix.stack_v2 }}
DD_PROFILING_OUTPUT_PPROF: ${{ github.workspace }}/prefix/artifacts/ddtrace_profile
DD_EXCEPTION_REPLAY_ENABLED: "1"
defaults:
run:
working-directory: ddtrace
Expand Down
31 changes: 17 additions & 14 deletions ddtrace/propagation/http.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import itertools
import re
import sys
from typing import Any # noqa:F401
Expand Down Expand Up @@ -912,21 +913,23 @@ def _inject(span_context: Context, headers: Dict[str, str]) -> None:
if not baggage_items:
return

if len(baggage_items) > DD_TRACE_BAGGAGE_MAX_ITEMS:
log.warning("Baggage item limit exceeded")
return

try:
header_value = ",".join(
f"{_BaggageHeader._encode_key(key)}={_BaggageHeader._encode_value(value)}"
for key, value in baggage_items
)

buf = bytes(header_value, "utf-8")
if len(buf) > DD_TRACE_BAGGAGE_MAX_BYTES:
log.warning("Baggage header size exceeded")
return

if len(baggage_items) > DD_TRACE_BAGGAGE_MAX_ITEMS:
log.warning("Baggage item limit exceeded, dropping excess items")
baggage_items = itertools.islice(baggage_items, DD_TRACE_BAGGAGE_MAX_ITEMS) # type: ignore

encoded_items: List[str] = []
total_size = 0
for key, value in baggage_items:
item = f"{_BaggageHeader._encode_key(key)}={_BaggageHeader._encode_value(value)}"
item_size = len(item.encode("utf-8")) + (1 if encoded_items else 0) # +1 for comma if not first item
if total_size + item_size > DD_TRACE_BAGGAGE_MAX_BYTES:
log.warning("Baggage header size exceeded, dropping excess items")
break # stop adding items when size limit is reached
encoded_items.append(item)
total_size += item_size

header_value = ",".join(encoded_items)
headers[_HTTP_HEADER_BAGGAGE] = header_value

except Exception:
Expand Down
10 changes: 10 additions & 0 deletions scripts/profiles/django-simple/k6-exc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import http from 'k6/http';

export const options = {
duration: '60s',
vus: 100,
};

export default function () {
const res = http.get('http://127.0.0.1:8080/polls/123/results/');
}
3 changes: 1 addition & 2 deletions scripts/profiles/django-simple/k6-load.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
duration: '60s',
vus: 100,
};

export default function () {
const res = http.get('http://127.0.0.1:8000/accounts/signup/');
const res = http.get('http://127.0.0.1:8080/polls/123/vote/');
}
100 changes: 62 additions & 38 deletions scripts/profiles/django-simple/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,59 +9,83 @@ AUSTIN_EXPOSURE=5 # sec

test -f ${PREFIX}/gunicorn.pid && (kill -9 `cat ${PREFIX}/gunicorn.pid` ; sleep 3) || rm -f ${PREFIX}/gunicorn.pid
pkill k6 || true
test -d ${PREFIX}/artifacts && rm -rf ${PREFIX}/artifacts || mkdir -p ${PREFIX}/artifacts
pkill -9 -f uwsgi || true
test -d ${PREFIX}/artifacts && rm -rf ${PREFIX}/artifacts
mkdir -p ${PREFIX}/artifacts

sudo echo "sudo OK"

sudo rm -f ${PREFIX}/uwsgi.pid

function profile_with_load {
name=${1}
scenario=${2}

echo "- profiling for ${name}"

sleep 3
${PREFIX}/k6*/k6 run --quiet scripts/profiles/django-simple/k6-load.js &
echo "Starting load"
${PREFIX}/k6*/k6 run --quiet scripts/profiles/django-simple/k6-${scenario}.js &
sleep 2
sudo `which austin` -bsCi ${AUSTIN_INTERVAL} -o ${PREFIX}/artifacts/${name}.mojo -p `cat ${PREFIX}/gunicorn.pid` -x ${AUSTIN_EXPOSURE}
LC_ALL=C sed -i 's|/home/runner/work/dd-trace-py/dd-trace-py/ddtrace/||g' ${PREFIX}/artifacts/${name}.mojo
echo "Attaching Austin to $(cat ${PREFIX}/uwsgi.pid)"
sudo `which austin` -bsCi ${AUSTIN_INTERVAL} -o ${PREFIX}/artifacts/${scenario}_${name}.mojo -p `cat ${PREFIX}/uwsgi.pid` -x ${AUSTIN_EXPOSURE}
LC_ALL=C sed -i 's|/home/runner/work/dd-trace-py/dd-trace-py/ddtrace/||g' ${PREFIX}/artifacts/${scenario}_${name}.mojo
echo "Stopping load"
pkill k6
}

source ${PREFIX}/bin/activate

export DJANGO_SETTINGS_MODULE="config.settings.production"
export DJANGO_ALLOWED_HOSTS="127.0.0.1"
export DJANGO_SECRET_KEY="SECRET_KEY"
export DATABASE_URL="sqlite:///django.db"
export DJANGO_ALLOWED_HOSTS="127.0.0.1"
export DEVELOPMENT_MODE=True

# Tag traces with HTTP headers to benchmark the related code
export DD_TRACE_HEADER_TAGS="User-Agent:http.user_agent,Referer:http.referer,Content-Type:http.content_type,Etag:http.etag"

# Baseline
pushd ${PREFIX}/trace-examples/python/django/django-simple
gunicorn config.wsgi --pid ${PREFIX}/gunicorn.pid > /dev/null &
echo "Done"
popd
profile_with_load "baseline"
kill $(cat ${PREFIX}/gunicorn.pid)

pushd ${PREFIX}/trace-examples/python/django/django-simple
ddtrace-run gunicorn config.wsgi --pid ${PREFIX}/gunicorn.pid > /dev/null &
popd
profile_with_load "head"
kill $(cat ${PREFIX}/gunicorn.pid)

sudo chown -R $(id -u):$(id -g) ${PREFIX}/artifacts/*

echo -n "Converting MOJO to Austin ... "
mojo2austin ${PREFIX}/artifacts/head.mojo ${PREFIX}/artifacts/head.austin.tmp
mojo2austin ${PREFIX}/artifacts/baseline.mojo ${PREFIX}/artifacts/baseline.austin.tmp
echo "[done]"

echo -n "Diffing ... "
python scripts/diff.py \
${PREFIX}/artifacts/head.austin.tmp \
${PREFIX}/artifacts/baseline.austin.tmp \
${PREFIX}/artifacts/baseline_head.diff
echo "[done]"

rm ${PREFIX}/artifacts/*.austin.tmp

head -n 25 ${PREFIX}/artifacts/baseline_head.diff.top

function run_scenario {
scenario=${1}

echo "Running scenario ${scenario}"

# Baseline
pushd ${PREFIX}/trace-examples/python/django/sample-django
uwsgi --http :8080 --enable-threads --module mysite.wsgi --pidfile ${PREFIX}/uwsgi.pid 2> /dev/null &
echo "Done"
popd
profile_with_load "baseline" ${scenario}
echo "Stopping uwsgi"
uwsgi --stop ${PREFIX}/uwsgi.pid
pkill -9 -f uwsgi || true

pushd ${PREFIX}/trace-examples/python/django/sample-django
uwsgi --http :8080 --enable-threads --module mysite.wsgi --pidfile ${PREFIX}/uwsgi.pid --import=ddtrace.bootstrap.sitecustomize 2> /dev/null &
popd
profile_with_load "head" ${scenario}
echo "Stopping uwsgi"
uwsgi --stop ${PREFIX}/uwsgi.pid
pkill -9 -f uwsgi || true

sudo chown -R $(id -u):$(id -g) ${PREFIX}/artifacts/*

echo -n "Converting MOJO to Austin ... "
mojo2austin ${PREFIX}/artifacts/${scenario}_head.mojo ${PREFIX}/artifacts/${scenario}_head.austin.tmp
mojo2austin ${PREFIX}/artifacts/${scenario}_baseline.mojo ${PREFIX}/artifacts/${scenario}_baseline.austin.tmp
echo "[done]"

echo -n "Diffing ... "
python scripts/diff.py \
${PREFIX}/artifacts/${scenario}_head.austin.tmp \
${PREFIX}/artifacts/${scenario}_baseline.austin.tmp \
${PREFIX}/artifacts/${scenario}_baseline_head.diff
echo "[done]"

rm ${PREFIX}/artifacts/*.austin.tmp

head -n 25 ${PREFIX}/artifacts/${scenario}_baseline_head.diff.top
}


run_scenario "load"
run_scenario "exc"
10 changes: 4 additions & 6 deletions scripts/profiles/django-simple/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ PREFIX=${1}
AUSTIN_VERSION="3.6"
K6_VERSION="0.26.2"

export DJANGO_SETTINGS_MODULE="config.settings.production"
export DJANGO_ALLOWED_HOSTS="127.0.0.1"
export DJANGO_SECRET_KEY="SECRET_KEY"
export DATABASE_URL="sqlite:///django.db"

# Clean up existing installation
Expand All @@ -26,12 +24,12 @@ source ${PREFIX}/bin/activate
pip install pip --upgrade

# Install the application
git clone https://github.com/DataDog/trace-examples.git ${PREFIX}/trace-examples
test -d ${PREFIX}/trace-examples || git clone -b sample-django --single-branch https://github.com/DataDog/trace-examples.git ${PREFIX}/trace-examples
pushd ${PREFIX}/trace-examples/
git checkout origin/django-simple
pushd python/django/django-simple
pip install -r requirements/production.txt
pushd python/django/sample-django
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic
popd
popd

Expand Down
7 changes: 7 additions & 0 deletions tests/contrib/botocore/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from ddtrace._trace._span_pointer import _SpanPointer
from ddtrace._trace._span_pointer import _SpanPointerDirection
from ddtrace._trace.utils_botocore import span_tags
from tests.utils import get_128_bit_trace_id_from_headers


Expand Down Expand Up @@ -104,6 +105,12 @@ def setUp(self):
super(BotocoreTest, self).setUp()

Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(botocore.parsers.ResponseParser)
# Setting the validated flag to False ensures the redaction paths configurations are re-validated
# FIXME: Ensure AWSPayloadTagging._REQUEST_REDACTION_PATHS_DEFAULTS is always in sync with
# config.botocore.payload_tagging_request
# FIXME: Ensure AWSPayloadTagging._RESPONSE_REDACTION_PATHS_DEFAULTS is always in sync with
# config.botocore.payload_tagging_response
span_tags._PAYLOAD_TAGGER.validated = False

def tearDown(self):
super(BotocoreTest, self).tearDown()
Expand Down
102 changes: 57 additions & 45 deletions tests/tracer/test_endpoint_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from http.client import HTTPResponse
from io import BytesIO
import logging
from unittest import mock

from ddtrace.internal.http import HTTPConnection
Expand Down Expand Up @@ -84,87 +85,98 @@ def mock_pass(self, *args, **kwargs):


def test_unset_config_endpoint(caplog):
caplog.set_level(10)
with override_env({"DD_TRACE_DEBUG": "true"}):
with caplog.at_level(logging.DEBUG, logger="ddtrace"):
assert fetch_config_from_endpoint() == {}
assert "Configuration endpoint not set. Skipping fetching configuration." in caplog.text
if caplog.text:
assert "Configuration endpoint not set. Skipping fetching configuration." in caplog.text


def test_set_config_endpoint_enabled(caplog):
caplog.set_level(10)
with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80", "DD_TRACE_DEBUG": "true"}), mock.patch.object(
HTTPConnection, "connect", new=mock_pass
), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object(
with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env(
{"_DD_CONFIG_ENDPOINT": "http://localhost:80"}
), mock.patch.object(HTTPConnection, "connect", new=mock_pass), mock.patch.object(
HTTPConnection, "send", new=mock_pass
), mock.patch.object(
HTTPConnection, "getresponse", new=mock_getresponse_enabled
):
assert fetch_config_from_endpoint() == {"dd_iast_enabled": True}
assert "Configuration endpoint not set. Skipping fetching configuration." not in caplog.text
assert "Failed to fetch configuration from endpoint" not in caplog.text
if caplog.text:
assert "Configuration endpoint not set. Skipping fetching configuration." not in caplog.text
assert "Failed to fetch configuration from endpoint" not in caplog.text


def test_set_config_endpoint_500(caplog):
caplog.set_level(10)
with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}), mock.patch.object(
HTTPConnection, "connect", new=mock_pass
), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object(
with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env(
{"_DD_CONFIG_ENDPOINT": "http://localhost:80"}
), mock.patch.object(HTTPConnection, "connect", new=mock_pass), mock.patch.object(
HTTPConnection, "send", new=mock_pass
), mock.patch.object(
HTTPConnection, "getresponse", new=mock_getresponse_500
):
assert fetch_config_from_endpoint() == {}
assert "Failed to fetch configuration from endpoint" in caplog.text
assert "RetryError: Response(status=500" in caplog.text
if caplog.text:
assert "Failed to fetch configuration from endpoint" in caplog.text
assert "RetryError: Response(status=500" in caplog.text


def test_set_config_endpoint_403(caplog):
caplog.set_level(10)
with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}), mock.patch.object(
HTTPConnection, "connect", new=mock_pass
), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object(
with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env(
{"_DD_CONFIG_ENDPOINT": "http://localhost:80"}
), mock.patch.object(HTTPConnection, "connect", new=mock_pass), mock.patch.object(
HTTPConnection, "send", new=mock_pass
), mock.patch.object(
HTTPConnection, "getresponse", new=mock_getresponse_403
):
assert fetch_config_from_endpoint() == {}
assert "Failed to fetch configuration from endpoint" in caplog.text
assert "RetryError: Response(status=403" in caplog.text
if caplog.text:
assert "Failed to fetch configuration from endpoint" in caplog.text
assert "RetryError: Response(status=403" in caplog.text


def test_set_config_endpoint_malformed(caplog):
caplog.set_level(10)
with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}), mock.patch.object(
HTTPConnection, "connect", new=mock_pass
), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object(
with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env(
{"_DD_CONFIG_ENDPOINT": "http://localhost:80"}
), mock.patch.object(HTTPConnection, "connect", new=mock_pass), mock.patch.object(
HTTPConnection, "send", new=mock_pass
), mock.patch.object(
HTTPConnection, "getresponse", new=mock_getresponse_malformed
):
assert fetch_config_from_endpoint() == {}
assert "Expecting property name enclosed in double quotes" in caplog.text
if caplog.text:
assert "Unable to parse Datadog Agent JSON" in caplog.text


def test_set_config_endpoint_connection_refused(caplog):
caplog.set_level(10)
with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}):
with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}):
assert fetch_config_from_endpoint() == {}
assert "Failed to fetch configuration from endpoint" in caplog.text
assert any(
message in caplog.text for message in ("Connection refused", "Address family not supported by protocol")
), "None of the expected connection error log messages were found"

if caplog.text:
assert "Failed to fetch configuration from endpoint" in caplog.text
assert any(
message in caplog.text for message in ("Connection refused", "Address family not supported by protocol")
), "None of the expected connection error log messages were found"


def test_set_config_endpoint_timeout_error(caplog):
caplog.set_level(10)
with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80", "DD_TRACE_DEBUG": "true"}), mock.patch(
"ddtrace.internal.utils.http.get_connection", side_effect=TimeoutError
):
with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env(
{"_DD_CONFIG_ENDPOINT": "http://localhost:80"}
), mock.patch("ddtrace.internal.utils.http.get_connection", side_effect=TimeoutError):
assert fetch_config_from_endpoint() == {}
assert "Configuration endpoint not set. Skipping fetching configuration." not in caplog.text
assert "Failed to fetch configuration from endpoint" in caplog.text
assert any(
message in caplog.text for message in ("Connection refused", "Address family not supported by protocol")
), "None of the expected connection error log messages were found"

if caplog.text:
assert "Configuration endpoint not set. Skipping fetching configuration." not in caplog.text
assert "Failed to fetch configuration from endpoint" in caplog.text
assert any(
message in caplog.text for message in ("Connection refused", "Address family not supported by protocol")
), "None of the expected connection error log messages were found"


def test_set_config_endpoint_retries(caplog):
caplog.set_level(10)
with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80", "DD_TRACE_DEBUG": "true"}), mock.patch.object(
HTTPConnection, "connect", new=mock_pass
), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object(
with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env(
{"_DD_CONFIG_ENDPOINT": "http://localhost:80"}
), mock.patch.object(HTTPConnection, "connect", new=mock_pass), mock.patch.object(
HTTPConnection, "send", new=mock_pass
), mock.patch.object(
HTTPConnection, "getresponse", new=mock_getresponse_enabled_after_4_retries
), mock.patch(
"ddtrace.settings.endpoint_config._get_retries", return_value=5
Expand Down
Loading

0 comments on commit edb7a1c

Please sign in to comment.