From 95af90d373c6bae2c596d5c22dbfd1b1597b4d7d Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 17 Nov 2025 15:43:28 -0500 Subject: [PATCH 01/11] move proto and util files to v2 directory --- mypy.ini | 8 +++---- pyproject.toml | 4 ++-- sgconfig.yml | 2 +- .../collector/lock_utils.py | 0 .../collector/pprof.proto | 0 .../collector/pprof_312_pb2.py | 0 .../collector/pprof_319_pb2.py | 0 .../collector/pprof_3_pb2.py | 0 .../collector/pprof_421_pb2.py | 0 .../collector/pprof_utils.py | 6 ++--- tests/profiling_v2/collector/test_asyncio.py | 6 ++--- tests/profiling_v2/collector/test_memalloc.py | 4 ++-- tests/profiling_v2/collector/test_stack.py | 6 ++--- .../collector/test_stack_asyncio.py | 14 ++++++------ .../profiling_v2/collector/test_threading.py | 22 +++++++++---------- .../collector/test_threading_asyncio.py | 6 ++--- tests/profiling_v2/test_accuracy.py | 2 +- tests/profiling_v2/test_gunicorn.py | 2 +- tests/profiling_v2/test_main.py | 4 ++-- tests/profiling_v2/test_pytorch.py | 2 +- tests/profiling_v2/test_uwsgi.py | 2 +- 21 files changed, 45 insertions(+), 45 deletions(-) rename tests/{profiling => profiling_v2}/collector/lock_utils.py (100%) rename tests/{profiling => profiling_v2}/collector/pprof.proto (100%) rename tests/{profiling => profiling_v2}/collector/pprof_312_pb2.py (100%) rename tests/{profiling => profiling_v2}/collector/pprof_319_pb2.py (100%) rename tests/{profiling => profiling_v2}/collector/pprof_3_pb2.py (100%) rename tests/{profiling => profiling_v2}/collector/pprof_421_pb2.py (100%) rename tests/{profiling => profiling_v2}/collector/pprof_utils.py (98%) diff --git a/mypy.ini b/mypy.ini index 882598d8256..5179adb6163 100644 --- a/mypy.ini +++ b/mypy.ini @@ -17,16 +17,16 @@ ignore_errors = true [mypy-ddtrace.vendor.*] ignore_errors = true -[mypy-tests.profiling.collector.pprof_3_pb2] +[mypy-tests.profiling_v2.collector.pprof_3_pb2] ignore_errors = true -[mypy-tests.profiling.collector.pprof_312_pb2] +[mypy-tests.profiling_v2.collector.pprof_312_pb2] ignore_errors = true -[mypy-tests.profiling.collector.pprof_319_pb2] +[mypy-tests.profiling_v2.collector.pprof_319_pb2] ignore_errors = true -[mypy-tests.profiling.collector.pprof_421_pb2] +[mypy-tests.profiling_v2.collector.pprof_421_pb2] ignore_errors = true [mypy-ddtrace.internal.datadog.profiling.ddup] diff --git a/pyproject.toml b/pyproject.toml index 4f6f85edc87..ac43112788c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,7 +136,7 @@ exclude = ''' | build/ | dist/ | tests/lib-injection/dd-lib-python-init-test-protobuf-old/addressbook_pb2.py$ - | tests/profiling/collector/pprof_.*_pb2.py$ + | tests/profiling_v2/collector/pprof_.*_pb2.py$ ) ''' @@ -186,7 +186,7 @@ exclude = [ "ddtrace/__init__.py", "ddtrace/vendor/*", "ddtrace/appsec/_iast/_taint_tracking/_vendor/*", - "tests/profiling/collector/pprof_*pb2.py", + "tests/profiling_v2/collector/pprof_*pb2.py", "tests/profiling/simple_program_gevent.py", "tests/contrib/grpc/hello_pb2.py", "tests/contrib/django_celery/app/*", diff --git a/sgconfig.yml b/sgconfig.yml index f39c95bb6de..0896551d707 100644 --- a/sgconfig.yml +++ b/sgconfig.yml @@ -58,7 +58,7 @@ ignoreFiles: - test-results/**/* # Generated protobuf files - - "tests/profiling/collector/pprof_*pb2.py" + - "tests/profiling_v2/collector/pprof_*pb2.py" - "tests/contrib/grpc/hello_pb2.py" - "tests/lib-injection/dd-lib-python-init-test-protobuf-old/addressbook_pb2.py" diff --git a/tests/profiling/collector/lock_utils.py b/tests/profiling_v2/collector/lock_utils.py similarity index 100% rename from tests/profiling/collector/lock_utils.py rename to tests/profiling_v2/collector/lock_utils.py diff --git a/tests/profiling/collector/pprof.proto b/tests/profiling_v2/collector/pprof.proto similarity index 100% rename from tests/profiling/collector/pprof.proto rename to tests/profiling_v2/collector/pprof.proto diff --git a/tests/profiling/collector/pprof_312_pb2.py b/tests/profiling_v2/collector/pprof_312_pb2.py similarity index 100% rename from tests/profiling/collector/pprof_312_pb2.py rename to tests/profiling_v2/collector/pprof_312_pb2.py diff --git a/tests/profiling/collector/pprof_319_pb2.py b/tests/profiling_v2/collector/pprof_319_pb2.py similarity index 100% rename from tests/profiling/collector/pprof_319_pb2.py rename to tests/profiling_v2/collector/pprof_319_pb2.py diff --git a/tests/profiling/collector/pprof_3_pb2.py b/tests/profiling_v2/collector/pprof_3_pb2.py similarity index 100% rename from tests/profiling/collector/pprof_3_pb2.py rename to tests/profiling_v2/collector/pprof_3_pb2.py diff --git a/tests/profiling/collector/pprof_421_pb2.py b/tests/profiling_v2/collector/pprof_421_pb2.py similarity index 100% rename from tests/profiling/collector/pprof_421_pb2.py rename to tests/profiling_v2/collector/pprof_421_pb2.py diff --git a/tests/profiling/collector/pprof_utils.py b/tests/profiling_v2/collector/pprof_utils.py similarity index 98% rename from tests/profiling/collector/pprof_utils.py rename to tests/profiling_v2/collector/pprof_utils.py index f6a2a4de6c4..3946b8d02f3 100644 --- a/tests/profiling/collector/pprof_utils.py +++ b/tests/profiling_v2/collector/pprof_utils.py @@ -12,7 +12,7 @@ import zstandard as zstd -from tests.profiling.collector.lock_utils import LineNo +from tests.profiling_v2.collector.lock_utils import LineNo UINT64_MAX = (1 << 64) - 1 @@ -34,12 +34,12 @@ def _protobuf_version() -> Tuple[int, int, int]: if _pb_version >= v: import sys - pprof_module = "tests.profiling.collector.pprof_%s%s_pb2" % v + pprof_module = "tests.profiling_v2.collector.pprof_%s%s_pb2" % v __import__(pprof_module) pprof_pb2 = sys.modules[pprof_module] break else: - from tests.profiling.collector import pprof_3_pb2 as pprof_pb2 # type: ignore[no-redef] + from tests.profiling_v2.collector import pprof_3_pb2 as pprof_pb2 # type: ignore[no-redef] # Clamp the value to the range [0, UINT64_MAX] as done in clamp_to_uint64_unsigned diff --git a/tests/profiling_v2/collector/test_asyncio.py b/tests/profiling_v2/collector/test_asyncio.py index c427a20f662..ecac824c943 100644 --- a/tests/profiling_v2/collector/test_asyncio.py +++ b/tests/profiling_v2/collector/test_asyncio.py @@ -9,10 +9,10 @@ from ddtrace import ext from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import asyncio as collector_asyncio -from tests.profiling.collector import pprof_utils from tests.profiling.collector import test_collector -from tests.profiling.collector.lock_utils import get_lock_linenos -from tests.profiling.collector.lock_utils import init_linenos +from tests.profiling_v2.collector import pprof_utils +from tests.profiling_v2.collector.lock_utils import get_lock_linenos +from tests.profiling_v2.collector.lock_utils import init_linenos init_linenos(__file__) diff --git a/tests/profiling_v2/collector/test_memalloc.py b/tests/profiling_v2/collector/test_memalloc.py index 2022636f72f..8e2bf29581a 100644 --- a/tests/profiling_v2/collector/test_memalloc.py +++ b/tests/profiling_v2/collector/test_memalloc.py @@ -7,7 +7,7 @@ from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import memalloc -from tests.profiling.collector import pprof_utils +from tests.profiling_v2.collector import pprof_utils PY_313_OR_ABOVE = sys.version_info[:2] >= (3, 13) @@ -28,7 +28,7 @@ def test_heap_samples_collected(): import os from ddtrace.profiling import Profiler - from tests.profiling.collector import pprof_utils + from tests.profiling_v2.collector import pprof_utils from tests.profiling_v2.collector.test_memalloc import _allocate_1k # Test for https://github.com/DataDog/dd-trace-py/issues/11069 diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index 20baf1eb265..4a43ffb5d65 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -11,8 +11,8 @@ from ddtrace import ext from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import stack -from tests.profiling.collector import pprof_utils from tests.profiling.collector import test_collector +from tests.profiling_v2.collector import pprof_utils # Python 3.11.9 is not compatible with gevent, https://github.com/gevent/gevent/issues/2040 @@ -35,8 +35,8 @@ def test_collect_truncate(): import os from ddtrace.profiling import profiler - from tests.profiling.collector import pprof_utils from tests.profiling.collector.test_stack import func1 + from tests.profiling_v2.collector import pprof_utils pprof_prefix = os.environ["DD_PROFILING_OUTPUT_PPROF"] output_filename = pprof_prefix + "." + str(os.getpid()) @@ -499,7 +499,7 @@ def test_collect_gevent_thread_task(): from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import stack - from tests.profiling.collector import pprof_utils + from tests.profiling_v2.collector import pprof_utils from tests.profiling_v2.collector.test_stack import _fib test_name = "test_collect_gevent_thread_task" diff --git a/tests/profiling_v2/collector/test_stack_asyncio.py b/tests/profiling_v2/collector/test_stack_asyncio.py index a13ea2aaad9..3c231cd5d28 100644 --- a/tests/profiling_v2/collector/test_stack_asyncio.py +++ b/tests/profiling_v2/collector/test_stack_asyncio.py @@ -18,7 +18,7 @@ def test_asyncio(): from ddtrace.internal.datadog.profiling import stack_v2 from ddtrace.profiling import profiler from ddtrace.trace import tracer - from tests.profiling.collector import pprof_utils + from tests.profiling_v2.collector import pprof_utils assert stack_v2.is_available, stack_v2.failure_msg @@ -188,7 +188,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling.collector import pprof_utils + from tests.profiling_v2.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) @@ -312,7 +312,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling.collector import pprof_utils + from tests.profiling_v2.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) @@ -437,7 +437,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling.collector import pprof_utils + from tests.profiling_v2.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) @@ -562,7 +562,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling.collector import pprof_utils + from tests.profiling_v2.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) @@ -689,7 +689,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling.collector import pprof_utils + from tests.profiling_v2.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) @@ -816,7 +816,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling.collector import pprof_utils + from tests.profiling_v2.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling_v2/collector/test_threading.py index fb4e0ad71cc..6ce4b3010e0 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling_v2/collector/test_threading.py @@ -21,12 +21,12 @@ from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector.threading import ThreadingLockCollector from ddtrace.profiling.collector.threading import ThreadingRLockCollector -from tests.profiling.collector import pprof_utils from tests.profiling.collector import test_collector -from tests.profiling.collector.lock_utils import LineNo -from tests.profiling.collector.lock_utils import get_lock_linenos -from tests.profiling.collector.lock_utils import init_linenos -from tests.profiling.collector.pprof_utils import pprof_pb2 +from tests.profiling_v2.collector import pprof_utils +from tests.profiling_v2.collector.lock_utils import LineNo +from tests.profiling_v2.collector.lock_utils import get_lock_linenos +from tests.profiling_v2.collector.lock_utils import init_linenos +from tests.profiling_v2.collector.pprof_utils import pprof_pb2 # Type aliases for supported classes @@ -205,9 +205,9 @@ def test_lock_gevent_tasks() -> None: from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector.threading import ThreadingLockCollector - from tests.profiling.collector import pprof_utils - from tests.profiling.collector.lock_utils import get_lock_linenos - from tests.profiling.collector.lock_utils import init_linenos + from tests.profiling_v2.collector import pprof_utils + from tests.profiling_v2.collector.lock_utils import get_lock_linenos + from tests.profiling_v2.collector.lock_utils import init_linenos assert ddup.is_available, "ddup is not available" @@ -297,9 +297,9 @@ def test_rlock_gevent_tasks() -> None: from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector.threading import ThreadingRLockCollector - from tests.profiling.collector import pprof_utils - from tests.profiling.collector.lock_utils import get_lock_linenos - from tests.profiling.collector.lock_utils import init_linenos + from tests.profiling_v2.collector import pprof_utils + from tests.profiling_v2.collector.lock_utils import get_lock_linenos + from tests.profiling_v2.collector.lock_utils import init_linenos assert ddup.is_available, "ddup is not available" diff --git a/tests/profiling_v2/collector/test_threading_asyncio.py b/tests/profiling_v2/collector/test_threading_asyncio.py index 8c09d6a348d..89d22e2aa66 100644 --- a/tests/profiling_v2/collector/test_threading_asyncio.py +++ b/tests/profiling_v2/collector/test_threading_asyncio.py @@ -15,9 +15,9 @@ def test_lock_acquire_events(): import threading from ddtrace.profiling import profiler - from tests.profiling.collector import pprof_utils - from tests.profiling.collector.lock_utils import get_lock_linenos - from tests.profiling.collector.lock_utils import init_linenos + from tests.profiling_v2.collector import pprof_utils + from tests.profiling_v2.collector.lock_utils import get_lock_linenos + from tests.profiling_v2.collector.lock_utils import init_linenos init_linenos(os.environ["DD_PROFILING_FILE_PATH"]) diff --git a/tests/profiling_v2/test_accuracy.py b/tests/profiling_v2/test_accuracy.py index 2333e429e55..3507b788ae9 100644 --- a/tests/profiling_v2/test_accuracy.py +++ b/tests/profiling_v2/test_accuracy.py @@ -67,7 +67,7 @@ def test_accuracy_stack_v2(): import os from ddtrace.profiling import profiler - from tests.profiling.collector import pprof_utils + from tests.profiling_v2.collector import pprof_utils from tests.profiling_v2.test_accuracy import assert_almost_equal from tests.profiling_v2.test_accuracy import spend_16 diff --git a/tests/profiling_v2/test_gunicorn.py b/tests/profiling_v2/test_gunicorn.py index 78297c85e55..8d99a36207b 100644 --- a/tests/profiling_v2/test_gunicorn.py +++ b/tests/profiling_v2/test_gunicorn.py @@ -8,7 +8,7 @@ import pytest -from tests.profiling.collector import pprof_utils +from tests.profiling_v2.collector import pprof_utils # DEV: gunicorn tests are hard to debug, so keeping these print statements for diff --git a/tests/profiling_v2/test_main.py b/tests/profiling_v2/test_main.py index 6920cb1d3b6..debd37213fc 100644 --- a/tests/profiling_v2/test_main.py +++ b/tests/profiling_v2/test_main.py @@ -5,8 +5,8 @@ import pytest -from tests.profiling.collector import lock_utils -from tests.profiling.collector import pprof_utils +from tests.profiling_v2.collector import lock_utils +from tests.profiling_v2.collector import pprof_utils from tests.utils import call_program diff --git a/tests/profiling_v2/test_pytorch.py b/tests/profiling_v2/test_pytorch.py index 659b9067562..673f5e25ac5 100644 --- a/tests/profiling_v2/test_pytorch.py +++ b/tests/profiling_v2/test_pytorch.py @@ -3,7 +3,7 @@ import pytest -from tests.profiling.collector import pprof_utils +from tests.profiling_v2.collector import pprof_utils from tests.utils import call_program diff --git a/tests/profiling_v2/test_uwsgi.py b/tests/profiling_v2/test_uwsgi.py index 6b5d4e7cf23..c2bc3fd83ab 100644 --- a/tests/profiling_v2/test_uwsgi.py +++ b/tests/profiling_v2/test_uwsgi.py @@ -9,7 +9,7 @@ import pytest from tests.contrib.uwsgi import run_uwsgi -from tests.profiling.collector import pprof_utils +from tests.profiling_v2.collector import pprof_utils # uwsgi is not available on Windows From 1074ba415931ab6915669fa0e51b6a8a73f6a6e7 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 17 Nov 2025 15:51:17 -0500 Subject: [PATCH 02/11] migrate collector tests to v2 --- tests/profiling/collector/test_memalloc.py | 350 ---------- tests/profiling/collector/test_stack.py | 621 ------------------ .../profiling/collector/test_stack_asyncio.py | 113 ---- .../collector/test_collector.py | 0 tests/profiling_v2/collector/test_memalloc.py | 343 ++++++++++ .../collector/test_sample_count.py | 0 tests/profiling_v2/collector/test_stack.py | 337 +++++++++- .../collector/test_task.py | 0 .../collector/test_traceback.py | 0 9 files changed, 679 insertions(+), 1085 deletions(-) delete mode 100644 tests/profiling/collector/test_memalloc.py delete mode 100644 tests/profiling/collector/test_stack.py delete mode 100644 tests/profiling/collector/test_stack_asyncio.py rename tests/{profiling => profiling_v2}/collector/test_collector.py (100%) rename tests/{profiling => profiling_v2}/collector/test_sample_count.py (100%) rename tests/{profiling => profiling_v2}/collector/test_task.py (100%) rename tests/{profiling => profiling_v2}/collector/test_traceback.py (100%) diff --git a/tests/profiling/collector/test_memalloc.py b/tests/profiling/collector/test_memalloc.py deleted file mode 100644 index 28043f02d92..00000000000 --- a/tests/profiling/collector/test_memalloc.py +++ /dev/null @@ -1,350 +0,0 @@ -# -*- encoding: utf-8 -*- -import gc -import os -import sys -import threading - -import pytest - -from ddtrace.internal.settings.profiling import ProfilingConfig -from ddtrace.internal.settings.profiling import _derive_default_heap_sample_size -from ddtrace.profiling.collector import memalloc -from ddtrace.profiling.event import DDFrame - - -try: - from ddtrace.profiling.collector import _memalloc -except ImportError: - pytestmark = pytest.mark.skip("_memalloc not available") - - -def test_start_twice(): - _memalloc.start(64, 512) - with pytest.raises(RuntimeError): - _memalloc.start(64, 512) - _memalloc.stop() - - -def test_start_wrong_arg(): - with pytest.raises(TypeError, match="function takes exactly 2 arguments \\(1 given\\)"): - _memalloc.start(2) - - with pytest.raises(ValueError, match="the number of frames must be in range \\[1; 65535\\]"): - _memalloc.start(429496, 1) - - with pytest.raises(ValueError, match="the number of frames must be in range \\[1; 65535\\]"): - _memalloc.start(-1, 1) - - with pytest.raises( - ValueError, - match="the heap sample size must be in range \\[0; 4294967295\\]", - ): - _memalloc.start(64, -1) - - with pytest.raises( - ValueError, - match="the heap sample size must be in range \\[0; 4294967295\\]", - ): - _memalloc.start(64, 345678909876) - - -def test_start_stop(): - _memalloc.start(1, 1) - _memalloc.stop() - - -def _allocate_1k(): - return [object() for _ in range(1000)] - - -_ALLOC_LINE_NUMBER = _allocate_1k.__code__.co_firstlineno + 1 - - -def _pre_allocate_1k(): - return _allocate_1k() - - -def test_iter_events(): - max_nframe = 32 - collector = memalloc.MemoryCollector(max_nframe=max_nframe, heap_sample_size=64) - with collector: - _allocate_1k() - samples = collector.test_snapshot() - alloc_samples = [s for s in samples if s.alloc_size > 0] - - total_alloc_count = sum(s.count for s in alloc_samples) - - assert total_alloc_count >= 1000 - # Watchout: if we dropped samples the test will likely fail - - object_count = 0 - for sample in alloc_samples: - stack = sample.frames - thread_id = sample.thread_id - size = sample.alloc_size - - assert 0 < len(stack) <= max_nframe - assert size >= 1 # size depends on the object size - last_call = stack[0] - if last_call == DDFrame( - __file__, - _ALLOC_LINE_NUMBER, - "" if sys.version_info < (3, 12) else "_allocate_1k", - "", - ): - assert thread_id == threading.main_thread().ident - if sys.version_info < (3, 12) and len(stack) > 1: - assert stack[1] == DDFrame(__file__, _ALLOC_LINE_NUMBER, "_allocate_1k", "") - object_count += sample.count - - assert object_count >= 1000 - - -def test_iter_events_dropped(): - max_nframe = 32 - collector = memalloc.MemoryCollector(max_nframe=max_nframe, heap_sample_size=64) - with collector: - _allocate_1k() - samples = collector.test_snapshot() - alloc_samples = [s for s in samples if s.alloc_size > 0] - - total_alloc_count = sum(s.count for s in alloc_samples) - - assert len(alloc_samples) > 0 - assert total_alloc_count >= 1000 - - -def test_iter_events_not_started(): - collector = memalloc.MemoryCollector() - samples = collector.test_snapshot() - assert samples == () - - -@pytest.mark.skipif( - os.getenv("DD_PROFILE_TEST_GEVENT", False), - reason="Test not compatible with gevent", -) -def test_iter_events_multi_thread(): - max_nframe = 32 - t = threading.Thread(target=_allocate_1k) - collector = memalloc.MemoryCollector(max_nframe=max_nframe, heap_sample_size=64) - with collector: - _allocate_1k() - t.start() - t.join() - - samples = collector.test_snapshot() - alloc_samples = [s for s in samples if s.alloc_size > 0] - - total_alloc_count = sum(s.count for s in alloc_samples) - - assert total_alloc_count >= 1000 - - # Watchout: if we dropped samples the test will likely fail - - count_object = 0 - count_thread = 0 - for sample in alloc_samples: - stack = sample.frames - thread_id = sample.thread_id - size = sample.alloc_size - - assert 0 < len(stack) <= max_nframe - assert size >= 1 # size depends on the object size - last_call = stack[0] - if last_call == DDFrame( - __file__, - _ALLOC_LINE_NUMBER, - "" if sys.version_info < (3, 12) else "_allocate_1k", - "", - ): - if thread_id == threading.main_thread().ident: - count_object += sample.count - if sys.version_info < (3, 12) and len(stack) > 1: - assert stack[1] == DDFrame(__file__, _ALLOC_LINE_NUMBER, "_allocate_1k", "") - elif thread_id == t.ident: - count_thread += sample.count - entry = 2 if sys.version_info < (3, 12) else 1 - if entry < len(stack): - assert stack[entry].file_name == threading.__file__ - assert stack[entry].lineno > 0 - assert stack[entry].function_name == "run" - - assert count_object >= 1000 - assert count_thread >= 1000 - - -def test_heap(): - max_nframe = 32 - collector = memalloc.MemoryCollector(max_nframe=max_nframe, heap_sample_size=1024) - with collector: - _test_heap_impl(collector, max_nframe) - - -def _test_heap_impl(collector, max_nframe): - x = _allocate_1k() - samples = collector.test_snapshot() - - alloc_samples = [s for s in samples if s.in_use_size > 0] - - # Check that at least one sample comes from the main thread - thread_found = False - - for sample in alloc_samples: - stack = sample.frames - thread_id = sample.thread_id - size = sample.in_use_size - - assert 0 < len(stack) <= max_nframe - assert size > 0 - - if thread_id == threading.main_thread().ident: - thread_found = True - assert isinstance(thread_id, int) - if stack[0] == DDFrame( - __file__, - _ALLOC_LINE_NUMBER, - "" if sys.version_info < (3, 12) else "_allocate_1k", - "", - ): - break - else: - pytest.fail("No trace of allocation in heap") - assert thread_found, "Main thread not found" - - y = _pre_allocate_1k() - samples = collector.test_snapshot() - - alloc_samples = [s for s in samples if s.in_use_size > 0] - - for sample in alloc_samples: - stack = sample.frames - thread_id = sample.thread_id - size = sample.in_use_size - - assert 0 < len(stack) <= max_nframe - assert size > 0 - assert isinstance(thread_id, int) - if stack[0] == DDFrame( - __file__, - _ALLOC_LINE_NUMBER, - "" if sys.version_info < (3, 12) else "_allocate_1k", - "", - ): - break - else: - pytest.fail("No trace of allocation in heap") - - del x - gc.collect() - - samples = collector.test_snapshot() - - alloc_samples = [s for s in samples if s.in_use_size > 0] - - for sample in alloc_samples: - stack = sample.frames - thread_id = sample.thread_id - size = sample.in_use_size - - assert 0 < len(stack) <= max_nframe - assert size > 0 - assert isinstance(thread_id, int) - entry = 2 if sys.version_info < (3, 12) else 1 - if ( - len(stack) > entry - and stack[0] - == DDFrame( - __file__, - _ALLOC_LINE_NUMBER, - "" if sys.version_info < (3, 12) else "_allocate_1k", - "", - ) - and stack[entry].function_name == "_test_heap_impl" - ): - pytest.fail("Allocated memory still in heap") - - del y - gc.collect() - - samples = collector.test_snapshot() - - alloc_samples = [s for s in samples if s.in_use_size > 0] - - for sample in alloc_samples: - stack = sample.frames - thread_id = sample.thread_id - size = sample.in_use_size - - assert 0 < len(stack) <= max_nframe - assert size > 0 - assert isinstance(thread_id, int) - if ( - len(stack) >= 3 - and stack[0].file_name == __file__ - and stack[0].lineno == _ALLOC_LINE_NUMBER - and stack[0].function_name in ("", "_allocate_1k") - and stack[1].file_name == __file__ - and stack[1].lineno == _ALLOC_LINE_NUMBER - and stack[1].function_name == "_allocate_1k" - and stack[2].file_name == __file__ - and stack[2].function_name == "_pre_allocate_1k" - ): - pytest.fail("Allocated memory still in heap") - - -def test_heap_stress(): - # This should run for a few seconds, and is enough to spot potential segfaults. - _memalloc.start(64, 1024) - try: - x = [] - - for _ in range(20): - for _ in range(1000): - x.append(object()) - _memalloc.heap() - del x[:100] - finally: - _memalloc.stop() - - -@pytest.mark.parametrize("heap_sample_size", (0, 512 * 1024, 1024 * 1024, 2048 * 1024, 4096 * 1024)) -def test_memalloc_speed(benchmark, heap_sample_size): - if heap_sample_size: - with memalloc.MemoryCollector(heap_sample_size=heap_sample_size): - benchmark(_allocate_1k) - else: - benchmark(_allocate_1k) - - -@pytest.mark.parametrize( - "enabled,predicates", - ( - ( - True, - ( - lambda v: v >= 512 * 1024, - lambda v: v > 1, - lambda v: v > 512, - lambda v: v == 512 * 1024 * 1024, - ), - ), - ( - False, - ( - lambda v: v == 0, - lambda v: v == 0, - lambda v: v == 0, - lambda v: v == 0, - ), - ), - ), -) -def test_memalloc_sample_size(enabled, predicates, monkeypatch): - monkeypatch.setenv("DD_PROFILING_HEAP_ENABLED", str(enabled).lower()) - config = ProfilingConfig() - - assert config.heap.enabled is enabled - - for predicate, default in zip(predicates, (1024 * 1024, 1, 512, 512 * 1024 * 1024)): - assert predicate(_derive_default_heap_sample_size(config.heap, default)) diff --git a/tests/profiling/collector/test_stack.py b/tests/profiling/collector/test_stack.py deleted file mode 100644 index b70ddd773e9..00000000000 --- a/tests/profiling/collector/test_stack.py +++ /dev/null @@ -1,621 +0,0 @@ -# -*- encoding: utf-8 -*- -import _thread -import os -import sys -import threading -import time -import uuid - -import pytest - -from ddtrace import ext -from ddtrace.internal.datadog.profiling import ddup -from ddtrace.profiling.collector import stack -from tests.conftest import get_original_test_name -from tests.profiling.collector import pprof_utils - -from . import test_collector - - -# Python 3.11.9 is not compatible with gevent, https://github.com/gevent/gevent/issues/2040 -# https://github.com/python/cpython/issues/117983 -# The fix was not backported to 3.11. The fix was first released in 3.12.5 for -# Python 3.12. Tested with Python 3.11.8 and 3.12.5 to confirm the issue. -TESTING_GEVENT = os.getenv("DD_PROFILE_TEST_GEVENT", False) and ( - sys.version_info < (3, 11, 9) or sys.version_info >= (3, 12, 5) -) - - -def func1(): - return func2() - - -def func2(): - return func3() - - -def func3(): - return func4() - - -def func4(): - return func5() - - -def func5(): - return time.sleep(1) - - -@pytest.mark.subprocess( - env=dict( - DD_PROFILING_MAX_FRAMES="5", - DD_PROFILING_OUTPUT_PPROF="/tmp/test_collect_truncate", - ) -) -def test_collect_truncate(): - import os - - from ddtrace.profiling import profiler - from tests.profiling.collector import pprof_utils - from tests.profiling.collector.test_stack import func1 - - pprof_prefix = os.environ["DD_PROFILING_OUTPUT_PPROF"] - output_filename = pprof_prefix + "." + str(os.getpid()) - - max_nframes = int(os.environ["DD_PROFILING_MAX_FRAMES"]) - - p = profiler.Profiler() - p.start() - - func1() - - p.stop() - - profile = pprof_utils.parse_newest_profile(output_filename) - samples = pprof_utils.get_samples_with_value_type(profile, "wall-time") - assert len(samples) > 0 - for sample in samples: - assert len(sample.location_id) <= max_nframes, len(sample.location_id) - - -def _find_sleep_event(events, class_name): - class_method_found = False - class_classmethod_found = False - - for e in events: - for frame in e.frames: - if frame[0] == __file__.replace(".pyc", ".py") and frame[2] == "sleep_class" and frame[3] == class_name: - class_method_found = True - elif ( - frame[0] == __file__.replace(".pyc", ".py") and frame[2] == "sleep_instance" and frame[3] == class_name - ): - class_classmethod_found = True - - if class_method_found and class_classmethod_found: - return True - - return False - - -def test_collect_once_with_class(tmp_path): - class SomeClass(object): - @classmethod - def sleep_class(cls): - return cls().sleep_instance() - - def sleep_instance(self): - for _ in range(10): - time.sleep(0.1) - - test_name = "test_collect_once_with_class" - pprof_prefix = str(tmp_path / test_name) - output_filename = pprof_prefix + "." + str(os.getpid()) - - assert ddup.is_available - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) - ddup.start() - - with stack.StackCollector(): - SomeClass.sleep_class() - - ddup.upload() - - profile = pprof_utils.parse_newest_profile(output_filename) - samples = pprof_utils.get_samples_with_value_type(profile, "wall-time") - assert len(samples) > 0 - - pprof_utils.assert_profile_has_sample( - profile, - samples=samples, - expected_sample=pprof_utils.StackEvent( - thread_id=_thread.get_ident(), - thread_name="MainThread", - locations=[ - pprof_utils.StackLocation( - function_name="sleep_instance", - filename="test_stack.py", - line_no=SomeClass.sleep_instance.__code__.co_firstlineno + 2, - ), - pprof_utils.StackLocation( - function_name="sleep_class", - filename="test_stack.py", - line_no=SomeClass.sleep_class.__code__.co_firstlineno + 2, - ), - pprof_utils.StackLocation( - function_name="test_collect_once_with_class", - filename="test_stack.py", - line_no=test_collect_once_with_class.__code__.co_firstlineno + 19, - ), - ], - ), - ) - - -def test_collect_once_with_class_not_right_type(tmp_path): - class SomeClass(object): - @classmethod - def sleep_class(foobar, cls): - return foobar().sleep_instance(cls) - - def sleep_instance(foobar, self): - for _ in range(10): - time.sleep(0.1) - - test_name = "test_collect_once_with_class" - pprof_prefix = str(tmp_path / test_name) - output_filename = pprof_prefix + "." + str(os.getpid()) - - assert ddup.is_available - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) - ddup.start() - - with stack.StackCollector(): - SomeClass.sleep_class(123) - - ddup.upload() - - profile = pprof_utils.parse_newest_profile(output_filename) - samples = pprof_utils.get_samples_with_value_type(profile, "wall-time") - assert len(samples) > 0 - - pprof_utils.assert_profile_has_sample( - profile, - samples=samples, - expected_sample=pprof_utils.StackEvent( - thread_id=_thread.get_ident(), - thread_name="MainThread", - locations=[ - pprof_utils.StackLocation( - function_name="sleep_instance", - filename="test_stack.py", - line_no=SomeClass.sleep_instance.__code__.co_firstlineno + 2, - ), - pprof_utils.StackLocation( - function_name="sleep_class", - filename="test_stack.py", - line_no=SomeClass.sleep_class.__code__.co_firstlineno + 2, - ), - pprof_utils.StackLocation( - function_name="test_collect_once_with_class_not_right_type", - filename="test_stack.py", - line_no=test_collect_once_with_class_not_right_type.__code__.co_firstlineno + 19, - ), - ], - ), - ) - - -def _fib(n): - if n == 1: - return 1 - elif n == 0: - return 0 - else: - return _fib(n - 1) + _fib(n - 2) - - -@pytest.mark.skipif(not TESTING_GEVENT, reason="Not testing gevent") -@pytest.mark.subprocess(ddtrace_run=True) -def test_collect_gevent_thread_task(): - from gevent import monkey # noqa:F401 - - monkey.patch_all() - - import os - import threading - import time - - from ddtrace.internal.datadog.profiling import ddup - from ddtrace.profiling.collector import stack - from tests.profiling.collector import pprof_utils - from tests.profiling.collector.test_stack import _fib - - test_name = "test_collect_gevent_thread_task" - pprof_prefix = "/tmp/" + test_name - output_filename = pprof_prefix + "." + str(os.getpid()) - - assert ddup.is_available - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) - ddup.start() - - # Start some (green)threads - def _dofib(): - for _ in range(10): - # spend some time in CPU so the profiler can catch something - _fib(28) - # Just make sure gevent switches threads/greenlets - time.sleep(0) - - threads = [] - with stack.StackCollector(): - for i in range(10): - t = threading.Thread(target=_dofib, name="TestThread %d" % i) - t.start() - threads.append(t) - for t in threads: - t.join() - - ddup.upload() - - expected_task_ids = {thread.ident for thread in threads} - - profile = pprof_utils.parse_newest_profile(output_filename) - samples = pprof_utils.get_samples_with_label_key(profile, "task id") - assert len(samples) > 0 - - checked_thread = False - - for sample in samples: - task_id_label = pprof_utils.get_label_with_key(profile.string_table, sample, "task id") - task_id = int(task_id_label.num) - if task_id in expected_task_ids: - pprof_utils.assert_stack_event( - profile, - sample, - pprof_utils.StackEvent( - task_name=r"TestThread \d+$", - task_id=task_id, - ), - ) - checked_thread = True - - assert checked_thread, "No samples found for the expected threads" - - -def test_repr(): - test_collector._test_repr( - stack.StackCollector, - "StackCollector(status=, nframes=64, tracer=None)", - ) - - -# Function to use for stress-test of polling -MAX_FN_NUM = 30 -FN_TEMPLATE = """def _f{num}(): - return _f{nump1}()""" - -for num in range(MAX_FN_NUM): - exec(FN_TEMPLATE.format(num=num, nump1=num + 1)) - -exec( - """def _f{MAX_FN_NUM}(): - try: - raise ValueError('test') - except Exception: - time.sleep(2)""".format( - MAX_FN_NUM=MAX_FN_NUM - ) -) - - -def test_stress_threads_run_as_thread(tmp_path): - test_name = "test_stress_threads_run_as_thread" - pprof_prefix = str(tmp_path / test_name) - output_filename = pprof_prefix + "." + str(os.getpid()) - - assert ddup.is_available - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) - ddup.start() - - quit_thread = threading.Event() - - def wait_for_quit(): - quit_thread.wait() - - with stack.StackCollector(): - NB_THREADS = 40 - - threads = [] - for _ in range(NB_THREADS): - t = threading.Thread(target=wait_for_quit) - t.start() - threads.append(t) - - time.sleep(3) - - quit_thread.set() - for t in threads: - t.join() - - ddup.upload() - - profile = pprof_utils.parse_newest_profile(output_filename) - samples = pprof_utils.get_samples_with_value_type(profile, "cpu-time") - assert len(samples) > 0 - - -# if you don't need to check the output profile, you can use this fixture -@pytest.fixture -def tracer_and_collector(tracer, request, tmp_path): - test_name = get_original_test_name(request) - pprof_prefix = str(tmp_path / test_name) - - assert ddup.is_available - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) - ddup.start() - - c = stack.StackCollector(tracer=tracer) - c.start() - try: - yield tracer, c - finally: - c.stop() - ddup.upload(tracer=tracer) - - -def test_collect_span_id(tracer, tmp_path): - test_name = "test_collect_span_id" - pprof_prefix = str(tmp_path / test_name) - output_filename = pprof_prefix + "." + str(os.getpid()) - - assert ddup.is_available - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) - ddup.start() - - tracer._endpoint_call_counter_span_processor.enable() - with stack.StackCollector(tracer=tracer): - resource = str(uuid.uuid4()) - span_type = ext.SpanTypes.WEB - with tracer.start_span("foobar", activate=True, resource=resource, span_type=span_type) as span: - for _ in range(10): - time.sleep(0.1) - span_id = span.span_id - local_root_span_id = span._local_root.span_id - - ddup.upload(tracer=tracer) - - profile = pprof_utils.parse_newest_profile(output_filename) - samples = pprof_utils.get_samples_with_label_key(profile, "trace endpoint") - pprof_utils.assert_profile_has_sample( - profile, - samples, - expected_sample=pprof_utils.StackEvent( - thread_id=_thread.get_ident(), - span_id=span_id, - trace_type=span_type, - local_root_span_id=local_root_span_id, - trace_endpoint=resource, - locations=[ - pprof_utils.StackLocation( - filename=os.path.basename(__file__), - function_name=test_name, - line_no=test_collect_span_id.__code__.co_firstlineno + 15, - ) - ], - ), - ) - - -def test_collect_span_resource_after_finish(tracer, tmp_path, request): - test_name = get_original_test_name(request) - pprof_prefix = str(tmp_path / test_name) - output_filename = pprof_prefix + "." + str(os.getpid()) - - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) - ddup.start() - - tracer._endpoint_call_counter_span_processor.enable() - with stack.StackCollector(tracer=tracer): - resource = str(uuid.uuid4()) - span_type = ext.SpanTypes.WEB - span = tracer.start_span("foobar", activate=True, span_type=span_type, resource=resource) - for _ in range(10): - time.sleep(0.1) - ddup.upload(tracer=tracer) - span.finish() - - profile = pprof_utils.parse_newest_profile(output_filename) - samples = profile.sample - pprof_utils.assert_profile_has_sample( - profile, - samples, - expected_sample=pprof_utils.StackEvent( - thread_id=_thread.get_ident(), - span_id=span.span_id, - trace_type=span_type, - # Looks like the endpoint is not collected if the span is not finished - # trace_endpoint=resource, - locations=[ - pprof_utils.StackLocation( - filename=os.path.basename(__file__), - function_name=test_name, - line_no=test_collect_span_resource_after_finish.__code__.co_firstlineno + 14, - ) - ], - ), - ) - - -def test_resource_not_collected(tmp_path, tracer): - test_name = "test_resource_not_collected" - pprof_prefix = str(tmp_path / test_name) - output_filename = pprof_prefix + "." + str(os.getpid()) - - assert ddup.is_available - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) - ddup.start() - - with stack.StackCollector(tracer=tracer): - resource = str(uuid.uuid4()) - span_type = ext.SpanTypes.WEB - with tracer.start_span("foobar", activate=True, resource=resource, span_type=span_type) as span: - _fib(28) - - ddup.upload(tracer=tracer) - - profile = pprof_utils.parse_newest_profile(output_filename) - pprof_utils.assert_profile_has_sample( - profile, - profile.sample, - expected_sample=pprof_utils.StackEvent( - thread_id=_thread.get_ident(), - span_id=span.span_id, - trace_type=span_type, - locations=[ - pprof_utils.StackLocation( - filename=os.path.basename(__file__), - function_name=test_name, - line_no=test_resource_not_collected.__code__.co_firstlineno + 13, - ) - ], - ), - ) - - -def test_collect_nested_span_id(tmp_path, tracer, request): - test_name = get_original_test_name(request) - pprof_prefix = str(tmp_path / test_name) - output_filename = pprof_prefix + "." + str(os.getpid()) - - assert ddup.is_available - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) - ddup.start() - - tracer._endpoint_call_counter_span_processor.enable() - with stack.StackCollector(tracer=tracer): - resource = str(uuid.uuid4()) - span_type = ext.SpanTypes.WEB - with tracer.start_span("foobar", activate=True, resource=resource, span_type=span_type): - with tracer.start_span("foobar", activate=True, resource=resource, span_type=span_type) as child_span: - for _ in range(10): - time.sleep(0.1) - ddup.upload(tracer=tracer) - - profile = pprof_utils.parse_newest_profile(output_filename) - samples = pprof_utils.get_samples_with_label_key(profile, "span id") - pprof_utils.assert_profile_has_sample( - profile, - samples, - expected_sample=pprof_utils.StackEvent( - thread_id=_thread.get_ident(), - span_id=child_span.span_id, - trace_type=span_type, - local_root_span_id=child_span._local_root.span_id, - trace_endpoint=resource, - locations=[ - pprof_utils.StackLocation( - filename=os.path.basename(__file__), - function_name=test_name, - line_no=test_collect_nested_span_id.__code__.co_firstlineno + 16, - ) - ], - ), - ) - - -def test_stress_trace_collection(tracer_and_collector): - tracer, _ = tracer_and_collector - - def _trace(): - for _ in range(5000): - with tracer.trace("hello"): - time.sleep(0.001) - - NB_THREADS = 30 - - threads = [] - for _ in range(NB_THREADS): - t = threading.Thread(target=_trace) - threads.append(t) - - for t in threads: - t.start() - - for t in threads: - t.join() - - -@pytest.mark.skipif(not TESTING_GEVENT or sys.version_info < (3, 9), reason="Not testing gevent") -@pytest.mark.subprocess(ddtrace_run=True) -def test_collect_gevent_threads(): - import gevent.monkey - - gevent.monkey.patch_all() - - import os - import threading - import time - - from ddtrace.internal.datadog.profiling import ddup - from ddtrace.profiling.collector import stack - from tests.profiling.collector import pprof_utils - - iteration = 100 - sleep_time = 0.01 - nb_threads = 15 - - # Start some greenthreads: they do nothing we just keep switching between them. - def _nothing(): - for _ in range(iteration): - # Do nothing and just switch to another greenlet - time.sleep(sleep_time) - - test_name = "test_collect_gevent_threads" - pprof_prefix = "/tmp/" + test_name - output_filename = pprof_prefix + "." + str(os.getpid()) - - assert ddup.is_available - ddup.config(env="test", service="test_collect_gevent_threads", version="my_version", output_filename=pprof_prefix) - ddup.start() - - with stack.StackCollector(): - threads = [] - i_to_tid = {} - for i in range(nb_threads): - t = threading.Thread(target=_nothing, name="TestThread %d" % i) - i_to_tid[i] = t.ident - t.start() - threads.append(t) - for t in threads: - t.join() - - ddup.upload() - - profile = pprof_utils.parse_newest_profile(output_filename) - samples = pprof_utils.get_samples_with_label_key(profile, "task name") - assert len(samples) > 0 - - for task_id in range(nb_threads): - pprof_utils.assert_profile_has_sample( - profile, - samples, - expected_sample=pprof_utils.StackEvent( - task_name="TestThread %d" % task_id, - task_id=i_to_tid[task_id], - thread_id=i_to_tid[task_id], - locations=[ - pprof_utils.StackLocation( - filename="test_stack.py", - function_name="_nothing", - line_no=_nothing.__code__.co_firstlineno + 3, - ) - ], - ), - ) - pprof_utils.assert_profile_has_sample( - profile, - samples, - expected_sample=pprof_utils.StackEvent( - task_name="MainThread", - ), - ) diff --git a/tests/profiling/collector/test_stack_asyncio.py b/tests/profiling/collector/test_stack_asyncio.py deleted file mode 100644 index f7d4ebd6cac..00000000000 --- a/tests/profiling/collector/test_stack_asyncio.py +++ /dev/null @@ -1,113 +0,0 @@ -import pytest - - -@pytest.mark.subprocess( - env=dict( - DD_PROFILING_OUTPUT_PPROF="/tmp/test_stack_asyncio", - ), - err=None, -) -def test_asyncio(): - import asyncio - import os - import time - import uuid - - from ddtrace import ext - from ddtrace.profiling import profiler - from ddtrace.trace import tracer - from tests.profiling.collector import pprof_utils - - sleep_time = 0.2 - loop_run_time = 3 - - async def stuff() -> None: - start_time = time.time() - while time.time() < start_time + loop_run_time: - await asyncio.sleep(sleep_time) - - async def hello(): - t1 = asyncio.create_task(stuff(), name="sleep 1") - t2 = asyncio.create_task(stuff(), name="sleep 2") - await stuff() - return (t1, t2) - - resource = str(uuid.uuid4()) - span_type = ext.SpanTypes.WEB - - p = profiler.Profiler(tracer=tracer) - p.start() - with tracer.trace("test_asyncio", resource=resource, span_type=span_type): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - maintask = loop.create_task(hello(), name="main") - - t1, t2 = loop.run_until_complete(maintask) - p.stop() - - t1_name = t1.get_name() - t2_name = t2.get_name() - - assert t1_name == "sleep 1" - assert t2_name == "sleep 2" - - output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) - - profile = pprof_utils.parse_newest_profile(output_filename) - - samples_with_span_id = pprof_utils.get_samples_with_label_key(profile, "span id") - assert len(samples_with_span_id) > 0 - - # get samples with task_name - samples = pprof_utils.get_samples_with_label_key(profile, "task name") - assert len(samples) > 0 - - pprof_utils.assert_profile_has_sample( - profile, - samples, - expected_sample=pprof_utils.StackEvent( - thread_name="MainThread", - task_name="main", - # Noticed that these are not set in Stack v1 as it doesn't propagate - # span correlation information. Stack v2 does. - # span_id=span_id, - # local_root_span_id=local_root_span_id, - locations=[ - pprof_utils.StackLocation( - function_name="hello", filename="test_stack_asyncio.py", line_no=hello.__code__.co_firstlineno + 3 - ) - ], - ), - ) - - pprof_utils.assert_profile_has_sample( - profile, - samples, - expected_sample=pprof_utils.StackEvent( - thread_name="MainThread", - task_name=t1_name, - # span_id=span_id, - # local_root_span_id=local_root_span_id, - locations=[ - pprof_utils.StackLocation( - function_name="stuff", filename="test_stack_asyncio.py", line_no=stuff.__code__.co_firstlineno + 3 - ), - ], - ), - ) - - pprof_utils.assert_profile_has_sample( - profile, - samples, - expected_sample=pprof_utils.StackEvent( - thread_name="MainThread", - task_name=t2_name, - # span_id=span_id, - # local_root_span_id=local_root_span_id, - locations=[ - pprof_utils.StackLocation( - function_name="stuff", filename="test_stack_asyncio.py", line_no=stuff.__code__.co_firstlineno + 3 - ), - ], - ), - ) diff --git a/tests/profiling/collector/test_collector.py b/tests/profiling_v2/collector/test_collector.py similarity index 100% rename from tests/profiling/collector/test_collector.py rename to tests/profiling_v2/collector/test_collector.py diff --git a/tests/profiling_v2/collector/test_memalloc.py b/tests/profiling_v2/collector/test_memalloc.py index 8e2bf29581a..2870932d7a4 100644 --- a/tests/profiling_v2/collector/test_memalloc.py +++ b/tests/profiling_v2/collector/test_memalloc.py @@ -1,3 +1,4 @@ +import gc import inspect import os import sys @@ -7,6 +8,7 @@ from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import memalloc +from ddtrace.profiling.event import DDFrame from tests.profiling_v2.collector import pprof_utils @@ -797,3 +799,344 @@ def worker(): assert ( worker_samples > 0 ), "Thread lifecycle test: Should capture allocations even as threads are created/destroyed" + + +# Tests from tests/profiling/collector/test_memalloc.py (v1) +def _pre_allocate_1k(): + return _allocate_1k() + + +def _test_heap_impl(collector, max_nframe): + x = _allocate_1k() + samples = collector.test_snapshot() + + alloc_samples = [s for s in samples if s.in_use_size > 0] + + # Check that at least one sample comes from the main thread + thread_found = False + + for sample in alloc_samples: + stack = sample.frames + thread_id = sample.thread_id + size = sample.in_use_size + + assert 0 < len(stack) <= max_nframe + assert size > 0 + + if thread_id == threading.main_thread().ident: + thread_found = True + assert isinstance(thread_id, int) + if stack[0] == DDFrame( + __file__, + _ALLOC_LINE_NUMBER, + "" if sys.version_info < (3, 12) else "_allocate_1k", + "", + ): + break + else: + pytest.fail("No trace of allocation in heap") + assert thread_found, "Main thread not found" + + y = _pre_allocate_1k() + samples = collector.test_snapshot() + + alloc_samples = [s for s in samples if s.in_use_size > 0] + + for sample in alloc_samples: + stack = sample.frames + thread_id = sample.thread_id + size = sample.in_use_size + + assert 0 < len(stack) <= max_nframe + assert size > 0 + assert isinstance(thread_id, int) + if stack[0] == DDFrame( + __file__, + _ALLOC_LINE_NUMBER, + "" if sys.version_info < (3, 12) else "_allocate_1k", + "", + ): + break + else: + pytest.fail("No trace of allocation in heap") + + del x + gc.collect() + + samples = collector.test_snapshot() + + alloc_samples = [s for s in samples if s.in_use_size > 0] + + for sample in alloc_samples: + stack = sample.frames + thread_id = sample.thread_id + size = sample.in_use_size + + assert 0 < len(stack) <= max_nframe + assert size > 0 + assert isinstance(thread_id, int) + entry = 2 if sys.version_info < (3, 12) else 1 + if ( + len(stack) > entry + and stack[0] + == DDFrame( + __file__, + _ALLOC_LINE_NUMBER, + "" if sys.version_info < (3, 12) else "_allocate_1k", + "", + ) + and stack[entry].function_name == "_test_heap_impl" + ): + pytest.fail("Allocated memory still in heap") + + del y + gc.collect() + + samples = collector.test_snapshot() + + alloc_samples = [s for s in samples if s.in_use_size > 0] + + for sample in alloc_samples: + stack = sample.frames + thread_id = sample.thread_id + size = sample.in_use_size + + assert 0 < len(stack) <= max_nframe + assert size > 0 + assert isinstance(thread_id, int) + if ( + len(stack) >= 3 + and stack[0].file_name == __file__ + and stack[0].lineno == _ALLOC_LINE_NUMBER + and stack[0].function_name in ("", "_allocate_1k") + and stack[1].file_name == __file__ + and stack[1].lineno == _ALLOC_LINE_NUMBER + and stack[1].function_name == "_allocate_1k" + and stack[2].file_name == __file__ + and stack[2].function_name == "_pre_allocate_1k" + ): + pytest.fail("Allocated memory still in heap") + + +def test_start_twice(): + from ddtrace.profiling.collector import _memalloc + + _memalloc.start(64, 512) + with pytest.raises(RuntimeError): + _memalloc.start(64, 512) + _memalloc.stop() + + +def test_start_wrong_arg(): + from ddtrace.profiling.collector import _memalloc + + with pytest.raises(TypeError, match="function takes exactly 2 arguments \\(1 given\\)"): + _memalloc.start(2) + + with pytest.raises(ValueError, match="the number of frames must be in range \\[1; 65535\\]"): + _memalloc.start(429496, 1) + + with pytest.raises(ValueError, match="the number of frames must be in range \\[1; 65535\\]"): + _memalloc.start(-1, 1) + + with pytest.raises( + ValueError, + match="the heap sample size must be in range \\[0; 4294967295\\]", + ): + _memalloc.start(64, -1) + + with pytest.raises( + ValueError, + match="the heap sample size must be in range \\[0; 4294967295\\]", + ): + _memalloc.start(64, 345678909876) + + +def test_start_stop(): + from ddtrace.profiling.collector import _memalloc + + _memalloc.start(1, 1) + _memalloc.stop() + + +def test_iter_events(): + from ddtrace.profiling.event import DDFrame + + max_nframe = 32 + collector = memalloc.MemoryCollector(max_nframe=max_nframe, heap_sample_size=64) + with collector: + _allocate_1k() + samples = collector.test_snapshot() + alloc_samples = [s for s in samples if s.alloc_size > 0] + + total_alloc_count = sum(s.count for s in alloc_samples) + + assert total_alloc_count >= 1000 + # Watchout: if we dropped samples the test will likely fail + + object_count = 0 + for sample in alloc_samples: + stack = sample.frames + thread_id = sample.thread_id + size = sample.alloc_size + + assert 0 < len(stack) <= max_nframe + assert size >= 1 # size depends on the object size + last_call = stack[0] + if last_call == DDFrame( + __file__, + _ALLOC_LINE_NUMBER, + "" if sys.version_info < (3, 12) else "_allocate_1k", + "", + ): + assert thread_id == threading.main_thread().ident + if sys.version_info < (3, 12) and len(stack) > 1: + assert stack[1] == DDFrame(__file__, _ALLOC_LINE_NUMBER, "_allocate_1k", "") + object_count += sample.count + + assert object_count >= 1000 + + +def test_iter_events_dropped(): + max_nframe = 32 + collector = memalloc.MemoryCollector(max_nframe=max_nframe, heap_sample_size=64) + with collector: + _allocate_1k() + samples = collector.test_snapshot() + alloc_samples = [s for s in samples if s.alloc_size > 0] + + total_alloc_count = sum(s.count for s in alloc_samples) + + assert len(alloc_samples) > 0 + assert total_alloc_count >= 1000 + + +def test_iter_events_not_started(): + collector = memalloc.MemoryCollector() + samples = collector.test_snapshot() + assert samples == () + + +@pytest.mark.skipif( + os.getenv("DD_PROFILE_TEST_GEVENT", False), + reason="Test not compatible with gevent", +) +def test_iter_events_multi_thread(): + from ddtrace.profiling.event import DDFrame + + max_nframe = 32 + t = threading.Thread(target=_allocate_1k) + collector = memalloc.MemoryCollector(max_nframe=max_nframe, heap_sample_size=64) + with collector: + _allocate_1k() + t.start() + t.join() + + samples = collector.test_snapshot() + alloc_samples = [s for s in samples if s.alloc_size > 0] + + total_alloc_count = sum(s.count for s in alloc_samples) + + assert total_alloc_count >= 1000 + + # Watchout: if we dropped samples the test will likely fail + + count_object = 0 + count_thread = 0 + for sample in alloc_samples: + stack = sample.frames + thread_id = sample.thread_id + size = sample.alloc_size + + assert 0 < len(stack) <= max_nframe + assert size >= 1 # size depends on the object size + last_call = stack[0] + if last_call == DDFrame( + __file__, + _ALLOC_LINE_NUMBER, + "" if sys.version_info < (3, 12) else "_allocate_1k", + "", + ): + if thread_id == threading.main_thread().ident: + count_object += sample.count + if sys.version_info < (3, 12) and len(stack) > 1: + assert stack[1] == DDFrame(__file__, _ALLOC_LINE_NUMBER, "_allocate_1k", "") + elif thread_id == t.ident: + count_thread += sample.count + entry = 2 if sys.version_info < (3, 12) else 1 + if entry < len(stack): + assert stack[entry].file_name == threading.__file__ + assert stack[entry].lineno > 0 + assert stack[entry].function_name == "run" + + assert count_object >= 1000 + assert count_thread >= 1000 + + +def test_heap(): + max_nframe = 32 + collector = memalloc.MemoryCollector(max_nframe=max_nframe, heap_sample_size=1024) + with collector: + _test_heap_impl(collector, max_nframe) + + +def test_heap_stress(): + from ddtrace.profiling.collector import _memalloc + + # This should run for a few seconds, and is enough to spot potential segfaults. + _memalloc.start(64, 1024) + try: + x = [] + + for _ in range(20): + for _ in range(1000): + x.append(object()) + _memalloc.heap() + del x[:100] + finally: + _memalloc.stop() + + +@pytest.mark.parametrize("heap_sample_size", (0, 512 * 1024, 1024 * 1024, 2048 * 1024, 4096 * 1024)) +def test_memalloc_speed(benchmark, heap_sample_size): + if heap_sample_size: + with memalloc.MemoryCollector(heap_sample_size=heap_sample_size): + benchmark(_allocate_1k) + else: + benchmark(_allocate_1k) + + +@pytest.mark.parametrize( + "enabled,predicates", + ( + ( + True, + ( + lambda v: v >= 512 * 1024, + lambda v: v > 1, + lambda v: v > 512, + lambda v: v == 512 * 1024 * 1024, + ), + ), + ( + False, + ( + lambda v: v == 0, + lambda v: v == 0, + lambda v: v == 0, + lambda v: v == 0, + ), + ), + ), +) +def test_memalloc_sample_size(enabled, predicates, monkeypatch): + from ddtrace.internal.settings.profiling import ProfilingConfig + from ddtrace.internal.settings.profiling import _derive_default_heap_sample_size + + monkeypatch.setenv("DD_PROFILING_HEAP_ENABLED", str(enabled).lower()) + config = ProfilingConfig() + + assert config.heap.enabled is enabled + + for predicate, default in zip(predicates, (1024 * 1024, 1, 512, 512 * 1024 * 1024)): + assert predicate(_derive_default_heap_sample_size(config.heap, default)) diff --git a/tests/profiling/collector/test_sample_count.py b/tests/profiling_v2/collector/test_sample_count.py similarity index 100% rename from tests/profiling/collector/test_sample_count.py rename to tests/profiling_v2/collector/test_sample_count.py diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index 4a43ffb5d65..f041904e6a9 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -11,6 +11,7 @@ from ddtrace import ext from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import stack +from tests.conftest import get_original_test_name from tests.profiling.collector import test_collector from tests.profiling_v2.collector import pprof_utils @@ -34,8 +35,9 @@ def test_collect_truncate(): import os - from ddtrace.profiling import profiler from tests.profiling.collector.test_stack import func1 + + from ddtrace.profiling import profiler from tests.profiling_v2.collector import pprof_utils pprof_prefix = os.environ["DD_PROFILING_OUTPUT_PPROF"] @@ -571,3 +573,336 @@ def test_repr(): stack.StackCollector, "StackCollector(status=, nframes=64, tracer=None)", ) + + +# Tests from tests/profiling/collector/test_stack.py (v1) +# Function to use for stress-test of polling +MAX_FN_NUM = 30 +FN_TEMPLATE = """def _f{num}(): + return _f{nump1}()""" + +for num in range(MAX_FN_NUM): + exec(FN_TEMPLATE.format(num=num, nump1=num + 1)) + +exec( + """def _f{MAX_FN_NUM}(): + try: + raise ValueError('test') + except Exception: + time.sleep(2)""".format( + MAX_FN_NUM=MAX_FN_NUM + ) +) + + +def test_stress_threads_run_as_thread(tmp_path): + test_name = "test_stress_threads_run_as_thread" + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + quit_thread = threading.Event() + + def wait_for_quit(): + quit_thread.wait() + + with stack.StackCollector(): + NB_THREADS = 40 + + threads = [] + for _ in range(NB_THREADS): + t = threading.Thread(target=wait_for_quit) + t.start() + threads.append(t) + + time.sleep(3) + + quit_thread.set() + for t in threads: + t.join() + + ddup.upload() + + profile = pprof_utils.parse_newest_profile(output_filename) + samples = pprof_utils.get_samples_with_value_type(profile, "cpu-time") + assert len(samples) > 0 + + +# if you don't need to check the output profile, you can use this fixture +@pytest.fixture +def tracer_and_collector(tracer, request, tmp_path): + test_name = get_original_test_name(request) + pprof_prefix = str(tmp_path / test_name) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + c = stack.StackCollector(tracer=tracer) + c.start() + try: + yield tracer, c + finally: + c.stop() + ddup.upload(tracer=tracer) + + +def test_collect_span_id(tracer, tmp_path): + test_name = "test_collect_span_id" + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + tracer._endpoint_call_counter_span_processor.enable() + with stack.StackCollector(tracer=tracer): + resource = str(uuid.uuid4()) + span_type = ext.SpanTypes.WEB + with tracer.start_span("foobar", activate=True, resource=resource, span_type=span_type) as span: + for _ in range(10): + time.sleep(0.1) + span_id = span.span_id + local_root_span_id = span._local_root.span_id + + ddup.upload(tracer=tracer) + + profile = pprof_utils.parse_newest_profile(output_filename) + samples = pprof_utils.get_samples_with_label_key(profile, "trace endpoint") + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_id=_thread.get_ident(), + span_id=span_id, + trace_type=span_type, + local_root_span_id=local_root_span_id, + trace_endpoint=resource, + locations=[ + pprof_utils.StackLocation( + filename=os.path.basename(__file__), + function_name=test_name, + line_no=test_collect_span_id.__code__.co_firstlineno + 15, + ) + ], + ), + ) + + +def test_collect_span_resource_after_finish(tracer, tmp_path, request): + test_name = get_original_test_name(request) + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + tracer._endpoint_call_counter_span_processor.enable() + with stack.StackCollector(tracer=tracer): + resource = str(uuid.uuid4()) + span_type = ext.SpanTypes.WEB + span = tracer.start_span("foobar", activate=True, span_type=span_type, resource=resource) + for _ in range(10): + time.sleep(0.1) + ddup.upload(tracer=tracer) + span.finish() + + profile = pprof_utils.parse_newest_profile(output_filename) + samples = profile.sample + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_id=_thread.get_ident(), + span_id=span.span_id, + trace_type=span_type, + # Looks like the endpoint is not collected if the span is not finished + # trace_endpoint=resource, + locations=[ + pprof_utils.StackLocation( + filename=os.path.basename(__file__), + function_name=test_name, + line_no=test_collect_span_resource_after_finish.__code__.co_firstlineno + 14, + ) + ], + ), + ) + + +def test_resource_not_collected(tmp_path, tracer): + test_name = "test_resource_not_collected" + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + with stack.StackCollector(tracer=tracer): + resource = str(uuid.uuid4()) + span_type = ext.SpanTypes.WEB + with tracer.start_span("foobar", activate=True, resource=resource, span_type=span_type) as span: + _fib(28) + + ddup.upload(tracer=tracer) + + profile = pprof_utils.parse_newest_profile(output_filename) + pprof_utils.assert_profile_has_sample( + profile, + profile.sample, + expected_sample=pprof_utils.StackEvent( + thread_id=_thread.get_ident(), + span_id=span.span_id, + trace_type=span_type, + locations=[ + pprof_utils.StackLocation( + filename=os.path.basename(__file__), + function_name=test_name, + line_no=test_resource_not_collected.__code__.co_firstlineno + 13, + ) + ], + ), + ) + + +def test_collect_nested_span_id(tmp_path, tracer, request): + test_name = get_original_test_name(request) + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + tracer._endpoint_call_counter_span_processor.enable() + with stack.StackCollector(tracer=tracer): + resource = str(uuid.uuid4()) + span_type = ext.SpanTypes.WEB + with tracer.start_span("foobar", activate=True, resource=resource, span_type=span_type): + with tracer.start_span("foobar", activate=True, resource=resource, span_type=span_type) as child_span: + for _ in range(10): + time.sleep(0.1) + ddup.upload(tracer=tracer) + + profile = pprof_utils.parse_newest_profile(output_filename) + samples = pprof_utils.get_samples_with_label_key(profile, "span id") + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_id=_thread.get_ident(), + span_id=child_span.span_id, + trace_type=span_type, + local_root_span_id=child_span._local_root.span_id, + trace_endpoint=resource, + locations=[ + pprof_utils.StackLocation( + filename=os.path.basename(__file__), + function_name=test_name, + line_no=test_collect_nested_span_id.__code__.co_firstlineno + 16, + ) + ], + ), + ) + + +def test_stress_trace_collection(tracer_and_collector): + tracer, _ = tracer_and_collector + + def _trace(): + for _ in range(5000): + with tracer.trace("hello"): + time.sleep(0.001) + + NB_THREADS = 30 + + threads = [] + for _ in range(NB_THREADS): + t = threading.Thread(target=_trace) + threads.append(t) + + for t in threads: + t.start() + + for t in threads: + t.join() + + +@pytest.mark.skipif(not TESTING_GEVENT or sys.version_info < (3, 9), reason="Not testing gevent") +@pytest.mark.subprocess(ddtrace_run=True) +def test_collect_gevent_threads(): + import gevent.monkey + + gevent.monkey.patch_all() + + import os + import threading + import time + + from ddtrace.internal.datadog.profiling import ddup + from ddtrace.profiling.collector import stack + from tests.profiling_v2.collector import pprof_utils + + iteration = 100 + sleep_time = 0.01 + nb_threads = 15 + + # Start some greenthreads: they do nothing we just keep switching between them. + def _nothing(): + for _ in range(iteration): + # Do nothing and just switch to another greenlet + time.sleep(sleep_time) + + test_name = "test_collect_gevent_threads" + pprof_prefix = "/tmp/" + test_name + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service="test_collect_gevent_threads", version="my_version", output_filename=pprof_prefix) + ddup.start() + + with stack.StackCollector(): + threads = [] + i_to_tid = {} + for i in range(nb_threads): + t = threading.Thread(target=_nothing, name="TestThread %d" % i) + i_to_tid[i] = t.ident + t.start() + threads.append(t) + for t in threads: + t.join() + + ddup.upload() + + profile = pprof_utils.parse_newest_profile(output_filename) + samples = pprof_utils.get_samples_with_label_key(profile, "task name") + assert len(samples) > 0 + + for task_id in range(nb_threads): + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + task_name="TestThread %d" % task_id, + task_id=i_to_tid[task_id], + thread_id=i_to_tid[task_id], + locations=[ + pprof_utils.StackLocation( + filename="test_stack.py", + function_name="_nothing", + line_no=_nothing.__code__.co_firstlineno + 3, + ) + ], + ), + ) + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + task_name="MainThread", + ), + ) diff --git a/tests/profiling/collector/test_task.py b/tests/profiling_v2/collector/test_task.py similarity index 100% rename from tests/profiling/collector/test_task.py rename to tests/profiling_v2/collector/test_task.py diff --git a/tests/profiling/collector/test_traceback.py b/tests/profiling_v2/collector/test_traceback.py similarity index 100% rename from tests/profiling/collector/test_traceback.py rename to tests/profiling_v2/collector/test_traceback.py From 8af10842a3e1df3f4d369b9636b00069d8e55e33 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 17 Nov 2025 15:51:34 -0500 Subject: [PATCH 03/11] delete directory --- tests/profiling/collector/__init__.py | 0 tests/profiling/collector/conftest.py | 22 ---------------------- 2 files changed, 22 deletions(-) delete mode 100644 tests/profiling/collector/__init__.py delete mode 100644 tests/profiling/collector/conftest.py diff --git a/tests/profiling/collector/__init__.py b/tests/profiling/collector/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/profiling/collector/conftest.py b/tests/profiling/collector/conftest.py deleted file mode 100644 index 8475c4d0a22..00000000000 --- a/tests/profiling/collector/conftest.py +++ /dev/null @@ -1,22 +0,0 @@ -import pytest - -import ddtrace -from ddtrace.profiling import Profiler -from tests.utils import override_global_config - - -@pytest.fixture -def tracer(): - with override_global_config(dict(_startup_logs_enabled=False)): - yield ddtrace.trace.tracer - - -@pytest.fixture -def profiler(monkeypatch): - monkeypatch.setenv("DD_PROFILING_API_TIMEOUT_MS", "100") - p = Profiler() - p.start() - try: - yield p - finally: - p.stop() From 5e719bfe19474eafef14f87c2e43212a24c55918 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 17 Nov 2025 15:58:26 -0500 Subject: [PATCH 04/11] move rest of the tests to v2 --- pyproject.toml | 1 - scripts/check_suitespec_coverage.py | 4 - tests/profiling/__init__.py | 0 tests/profiling/conftest.py | 11 -- tests/profiling/simple_program.py | 25 --- tests/profiling/simple_program_fork.py | 32 ---- tests/profiling/simple_program_gevent.py | 34 ---- tests/profiling/test_gunicorn.py | 66 -------- tests/profiling/test_main.py | 132 --------------- tests/profiling/test_profiler.py | 146 ----------------- tests/profiling/test_scheduler.py | 57 ------- tests/profiling/test_uwsgi.py | 150 ------------------ tests/profiling/utils.py | 11 -- .../_test_multiprocessing.py | 0 tests/profiling_v2/collector/test_asyncio.py | 2 +- tests/profiling_v2/collector/test_stack.py | 2 +- .../profiling_v2/collector/test_threading.py | 2 +- .../gevent_fork.py | 0 .../gunicorn-app.py | 0 tests/{profiling => profiling_v2}/run.py | 0 .../{profiling => profiling_v2}/suitespec.yml | 6 +- tests/profiling_v2/test_main.py | 6 +- tests/profiling_v2/test_scheduler.py | 5 + .../{profiling => profiling_v2}/uwsgi-app.py | 0 24 files changed, 14 insertions(+), 678 deletions(-) delete mode 100644 tests/profiling/__init__.py delete mode 100644 tests/profiling/conftest.py delete mode 100755 tests/profiling/simple_program.py delete mode 100644 tests/profiling/simple_program_fork.py delete mode 100644 tests/profiling/simple_program_gevent.py delete mode 100644 tests/profiling/test_gunicorn.py delete mode 100644 tests/profiling/test_main.py delete mode 100644 tests/profiling/test_profiler.py delete mode 100644 tests/profiling/test_scheduler.py delete mode 100644 tests/profiling/test_uwsgi.py delete mode 100644 tests/profiling/utils.py rename tests/{profiling => profiling_v2}/_test_multiprocessing.py (100%) rename tests/{profiling => profiling_v2}/gevent_fork.py (100%) rename tests/{profiling => profiling_v2}/gunicorn-app.py (100%) rename tests/{profiling => profiling_v2}/run.py (100%) rename tests/{profiling => profiling_v2}/suitespec.yml (95%) rename tests/{profiling => profiling_v2}/uwsgi-app.py (100%) diff --git a/pyproject.toml b/pyproject.toml index ac43112788c..ea4e742f98b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -187,7 +187,6 @@ exclude = [ "ddtrace/vendor/*", "ddtrace/appsec/_iast/_taint_tracking/_vendor/*", "tests/profiling_v2/collector/pprof_*pb2.py", - "tests/profiling/simple_program_gevent.py", "tests/contrib/grpc/hello_pb2.py", "tests/contrib/django_celery/app/*", "tests/contrib/protobuf/schemas/**/*.py", diff --git a/scripts/check_suitespec_coverage.py b/scripts/check_suitespec_coverage.py index 5594aeb4c51..c34029f0422 100755 --- a/scripts/check_suitespec_coverage.py +++ b/scripts/check_suitespec_coverage.py @@ -24,10 +24,6 @@ # Ignore any embedded documentation IGNORE_PATTERNS.add("**/*.md") -# TODO(taegyunkim): remove these after merging profiling v2 tests back to profiling -IGNORE_PATTERNS.add("tests/profiling/*.py") -IGNORE_PATTERNS.add("tests/profiling/*/*.py") -IGNORE_PATTERNS.add("tests/profiling/*/*.proto") def owners(path: str) -> str: diff --git a/tests/profiling/__init__.py b/tests/profiling/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/profiling/conftest.py b/tests/profiling/conftest.py deleted file mode 100644 index 6b04983dd8f..00000000000 --- a/tests/profiling/conftest.py +++ /dev/null @@ -1,11 +0,0 @@ -import os - -import pytest - - -@pytest.fixture(scope="session", autouse=True) -def disable_coverage_for_subprocess(): - try: - del os.environ["COV_CORE_SOURCE"] - except KeyError: - pass diff --git a/tests/profiling/simple_program.py b/tests/profiling/simple_program.py deleted file mode 100755 index 1ff264c7408..00000000000 --- a/tests/profiling/simple_program.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -from ddtrace.internal import service -from ddtrace.profiling import bootstrap -from ddtrace.profiling.collector import stack - - -for running_collector in bootstrap.profiler._profiler._collectors: - if isinstance(running_collector, stack.StackCollector): - break -else: - raise AssertionError("Unable to find stack collector") - - -print("hello world") -assert running_collector.status == service.ServiceStatus.RUNNING - -# Do some serious memory allocations! -for _ in range(5000000): - object() - -print(os.getpid()) -sys.exit(42) diff --git a/tests/profiling/simple_program_fork.py b/tests/profiling/simple_program_fork.py deleted file mode 100644 index 57f3bf81f64..00000000000 --- a/tests/profiling/simple_program_fork.py +++ /dev/null @@ -1,32 +0,0 @@ -import os -import sys -import threading - -from ddtrace.internal import service -import ddtrace.profiling.auto # noqa: F401 -import ddtrace.profiling.bootstrap -import ddtrace.profiling.profiler # noqa: F401 - - -lock = threading.Lock() -lock.acquire() - - -assert ddtrace.profiling.bootstrap.profiler.status == service.ServiceStatus.RUNNING - - -child_pid = os.fork() -if child_pid == 0: - # Release it - lock.release() - - # We track this one though - lock = threading.Lock() - lock.acquire() - lock.release() -else: - lock.release() - assert ddtrace.profiling.bootstrap.profiler.status == service.ServiceStatus.RUNNING - print(child_pid) - pid, status = os.waitpid(child_pid, 0) - sys.exit(os.WEXITSTATUS(status)) diff --git a/tests/profiling/simple_program_gevent.py b/tests/profiling/simple_program_gevent.py deleted file mode 100644 index f50fa3aa2e0..00000000000 --- a/tests/profiling/simple_program_gevent.py +++ /dev/null @@ -1,34 +0,0 @@ -# Import from ddtrace before monkey patching to ensure that we grab all the -# necessary references to the unpatched modules. -import ddtrace.auto # noqa: F401, I001 -import ddtrace.profiling.auto # noqa:F401 - - -import gevent.monkey # noqa:F402 - -gevent.monkey.patch_all() - -import threading # noqa: E402, F402, I001 -import time # noqa: E402, F402 - - -def fibonacci(n): - if n == 0: - return 0 - elif n == 1: - return 1 - else: - return fibonacci(n - 1) + fibonacci(n - 2) - - -i = 1 -for _ in range(20): - threads = [] - for _ in range(10): - t = threading.Thread(target=fibonacci, args=(i,)) - t.start() - threads.append(t) - i += 1 - for t in threads: - t.join() - time.sleep(0.1) diff --git a/tests/profiling/test_gunicorn.py b/tests/profiling/test_gunicorn.py deleted file mode 100644 index a21acaa523f..00000000000 --- a/tests/profiling/test_gunicorn.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- encoding: utf-8 -*- -import os -import re -import subprocess -import sys -import time - -import pytest - -from . import utils - - -# gunicorn is not available on Windows -if sys.platform == "win32": - pytestmark = pytest.mark.skip - -TESTING_GEVENT = os.getenv("DD_PROFILE_TEST_GEVENT", False) - - -def _run_gunicorn(*args): - cmd = ( - ["ddtrace-run", "gunicorn", "--bind", "127.0.0.1:7643", "--chdir", os.path.dirname(__file__)] - + list(args) - + ["gunicorn-app:app"] - ) - return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - - -@pytest.fixture -def gunicorn(monkeypatch): - # Do not ignore profiler so we have samples in the output pprof - monkeypatch.setenv("DD_PROFILING_IGNORE_PROFILER", "0") - monkeypatch.setenv("DD_PROFILING_ENABLED", "1") - - yield _run_gunicorn - - -def _get_worker_pids(stdout): - # type: (str) -> list[int] - return [int(_) for _ in re.findall(r"Booting worker with pid: (\d+)", stdout)] - - -def _test_gunicorn(gunicorn, tmp_path, monkeypatch, *args): - # type: (...) -> None - filename = str(tmp_path / "gunicorn.pprof") - monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) - - proc = gunicorn("-w", "3", *args) - time.sleep(3) - proc.terminate() - output = proc.stdout.read().decode() - worker_pids = _get_worker_pids(output) - - assert len(worker_pids) == 3, output - assert proc.wait() == 0, output - assert "module 'threading' has no attribute '_active'" not in output, output - - utils.check_pprof_file("%s.%d" % (filename, proc.pid)) - for pid in worker_pids: - utils.check_pprof_file("%s.%d" % (filename, pid)) - - -def test_gunicorn(gunicorn, tmp_path, monkeypatch): - # type: (...) -> None - args = ("-k", "gevent") if TESTING_GEVENT else tuple() - _test_gunicorn(gunicorn, tmp_path, monkeypatch, *args) diff --git a/tests/profiling/test_main.py b/tests/profiling/test_main.py deleted file mode 100644 index bb08b77e848..00000000000 --- a/tests/profiling/test_main.py +++ /dev/null @@ -1,132 +0,0 @@ -# -*- encoding: utf-8 -*- -import multiprocessing -import os -import sys -from typing import cast - -import pytest - -from tests.utils import call_program - -from . import utils - - -def test_call_script(monkeypatch): - # Set a very short timeout to exit fast - monkeypatch.setenv("DD_PROFILING_API_TIMEOUT_MS", "100") - monkeypatch.setenv("DD_PROFILING_ENABLED", "1") - stdout, stderr, exitcode, _ = call_program( - "ddtrace-run", sys.executable, os.path.join(os.path.dirname(__file__), "simple_program.py") - ) - if sys.platform == "win32": - assert exitcode == 0, (stdout, stderr) - else: - assert exitcode == 42, (stdout, stderr) - hello, pid = list(s.strip() for s in cast(bytes, stdout).decode().strip().split("\n")) - assert hello == "hello world", stdout.decode().strip() - - -@pytest.mark.skipif(not os.getenv("DD_PROFILE_TEST_GEVENT", False), reason="Not testing gevent") -def test_call_script_gevent(monkeypatch): - monkeypatch.setenv("DD_PROFILING_API_TIMEOUT_MS", "100") - stdout, stderr, exitcode, pid = call_program( - sys.executable, os.path.join(os.path.dirname(__file__), "simple_program_gevent.py") - ) - assert exitcode == 0, (stdout, stderr) - - -def test_call_script_pprof_output(tmp_path, monkeypatch): - """This checks if the pprof output and atexit register work correctly. - - The script does not run for one minute, so if the `stop_on_exit` flag is broken, this test will fail. - """ - filename = str(tmp_path / "pprof") - monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) - monkeypatch.setenv("DD_PROFILING_CAPTURE_PCT", "1") - monkeypatch.setenv("DD_PROFILING_ENABLED", "1") - stdout, stderr, exitcode, _ = call_program( - "ddtrace-run", sys.executable, os.path.join(os.path.dirname(__file__), "simple_program.py") - ) - if sys.platform == "win32": - assert exitcode == 0, (stdout, stderr) - else: - assert exitcode == 42, (stdout, stderr) - hello, pid = list(s.strip() for s in cast(bytes, stdout).decode().strip().split("\n")) - utils.check_pprof_file(filename + "." + str(pid)) - - -@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -def test_fork(tmp_path, monkeypatch): - filename = str(tmp_path / "pprof") - monkeypatch.setenv("DD_PROFILING_API_TIMEOUT_MS", "100") - monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) - monkeypatch.setenv("DD_PROFILING_CAPTURE_PCT", "100") - stdout, _, exitcode, pid = call_program("python", os.path.join(os.path.dirname(__file__), "simple_program_fork.py")) - assert exitcode == 0 - child_pid = cast(bytes, stdout).decode().strip() - utils.check_pprof_file(filename + "." + str(pid)) - utils.check_pprof_file(filename + "." + str(child_pid), sample_type="lock-release") - - -@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -@pytest.mark.skipif(not os.getenv("DD_PROFILE_TEST_GEVENT", False), reason="Not testing gevent") -def test_fork_gevent(monkeypatch): - monkeypatch.setenv("DD_PROFILING_API_TIMEOUT_MS", "100") - _, _, exitcode, _ = call_program("python", os.path.join(os.path.dirname(__file__), "gevent_fork.py")) - assert exitcode == 0 - - -methods = multiprocessing.get_all_start_methods() - - -@pytest.mark.parametrize( - "method", - set(methods) - {"forkserver", "fork"}, -) -def test_multiprocessing(method, tmp_path, monkeypatch): - filename = str(tmp_path / "pprof") - monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) - monkeypatch.setenv("DD_PROFILING_ENABLED", "1") - monkeypatch.setenv("DD_PROFILING_CAPTURE_PCT", "1") - stdout, stderr, exitcode, _ = call_program( - "ddtrace-run", - sys.executable, - os.path.join(os.path.dirname(__file__), "_test_multiprocessing.py"), - method, - ) - assert exitcode == 0, (stdout, stderr) - pid, child_pid = list(s.strip() for s in cast(bytes, stdout).decode().strip().split("\n")) - utils.check_pprof_file(filename + "." + str(pid)) - utils.check_pprof_file(filename + "." + str(child_pid), sample_type="wall-time") - - -@pytest.mark.subprocess( - ddtrace_run=True, - env=dict(DD_PROFILING_ENABLED="1"), - err=lambda _: "RuntimeError: the memalloc module is already started" not in _, -) -def test_memalloc_no_init_error_on_fork(): - import os - - pid = os.fork() - if not pid: - exit(0) - os.waitpid(pid, 0) - - -@pytest.mark.skipif(sys.version_info[:2] == (3, 9), reason="This test is flaky on Python 3.9") -@pytest.mark.subprocess( - ddtrace_run=True, - env=dict( - DD_PROFILING_ENABLED="1", - DD_UNLOAD_MODULES_FROM_SITECUSTOMIZE="1", - ), - out="OK\n", - err=None, -) -def test_profiler_start_up_with_module_clean_up_in_protobuf_app(): - # This can cause segfaults if we do module clean up with later versions of - # protobuf. This is a regression test. - from google.protobuf import empty_pb2 # noqa:F401 - - print("OK") diff --git a/tests/profiling/test_profiler.py b/tests/profiling/test_profiler.py deleted file mode 100644 index 7bcbb9351c4..00000000000 --- a/tests/profiling/test_profiler.py +++ /dev/null @@ -1,146 +0,0 @@ -import logging -import time -from unittest import mock - -import pytest - -import ddtrace -from ddtrace.profiling import collector -from ddtrace.profiling import profiler -from ddtrace.profiling import scheduler -from ddtrace.profiling.collector import asyncio -from ddtrace.profiling.collector import stack -from ddtrace.profiling.collector import threading - - -def test_status(): - p = profiler.Profiler() - assert repr(p.status) == "" - p.start() - assert repr(p.status) == "" - p.stop(flush=False) - assert repr(p.status) == "" - - -def test_restart(): - p = profiler.Profiler() - p.start() - p.stop(flush=False) - p.start() - p.stop(flush=False) - - -def test_multiple_stop(): - """Check that the profiler can be stopped twice.""" - p = profiler.Profiler() - p.start() - p.stop(flush=False) - p.stop(flush=False) - - -def test_tracer_api(monkeypatch): - monkeypatch.setenv("DD_API_KEY", "foobar") - prof = profiler.Profiler(tracer=ddtrace.tracer) - assert prof.tracer == ddtrace.tracer - for col in prof._profiler._collectors: - if isinstance(col, stack.StackCollector): - assert col.tracer == ddtrace.tracer - break - else: - pytest.fail("Unable to find stack collector") - - -@pytest.mark.subprocess() -def test_default_memory(): - from ddtrace.profiling import profiler - from ddtrace.profiling.collector import memalloc - - assert any(isinstance(col, memalloc.MemoryCollector) for col in profiler.Profiler()._profiler._collectors) - - -@pytest.mark.subprocess(env=dict(DD_PROFILING_MEMORY_ENABLED="true")) -def test_enable_memory(): - from ddtrace.profiling import profiler - from ddtrace.profiling.collector import memalloc - - assert any(isinstance(col, memalloc.MemoryCollector) for col in profiler.Profiler()._profiler._collectors) - - -@pytest.mark.subprocess(env=dict(DD_PROFILING_MEMORY_ENABLED="false")) -def test_disable_memory(): - from ddtrace.profiling import profiler - from ddtrace.profiling.collector import memalloc - - assert all(not isinstance(col, memalloc.MemoryCollector) for col in profiler.Profiler()._profiler._collectors) - - -def test_copy(): - p = profiler._ProfilerInstance(env="123", version="dwq", service="foobar") - c = p.copy() - assert c == p - assert p.env == c.env - assert p.version == c.version - assert p.service == c.service - assert p.tracer == c.tracer - assert p.tags == c.tags - - -def test_failed_start_collector(caplog, monkeypatch): - class ErrCollect(collector.Collector): - def _start_service(self): - raise RuntimeError("could not import required module") - - def _stop_service(self): - pass - - @staticmethod - def collect(): - pass - - @staticmethod - def snapshot(): - raise Exception("error!") - - monkeypatch.setenv("DD_PROFILING_UPLOAD_INTERVAL", "1") - - class TestProfiler(profiler._ProfilerInstance): - def _build_default_exporters(self, *args, **kargs): - return None - - p = TestProfiler() - err_collector = mock.MagicMock(wraps=ErrCollect()) - p._collectors = [err_collector] - p.start() - - def profiling_tuples(tuples): - return [t for t in tuples if t[0].startswith("ddtrace.profiling")] - - assert profiling_tuples(caplog.record_tuples) == [ - ("ddtrace.profiling.profiler", logging.ERROR, "Failed to start collector %r, disabling." % err_collector) - ] - time.sleep(2) - p.stop() - assert err_collector.snapshot.call_count == 0 - assert profiling_tuples(caplog.record_tuples) == [ - ("ddtrace.profiling.profiler", logging.ERROR, "Failed to start collector %r, disabling." % err_collector) - ] - - -def test_default_collectors(): - p = profiler.Profiler() - assert any(isinstance(c, stack.StackCollector) for c in p._profiler._collectors) - assert any(isinstance(c, threading.ThreadingLockCollector) for c in p._profiler._collectors) - try: - import asyncio as _ # noqa: F401 - except ImportError: - pass - else: - assert any(isinstance(c, asyncio.AsyncioLockCollector) for c in p._profiler._collectors) - p.stop(flush=False) - - -def test_profiler_serverless(monkeypatch): - monkeypatch.setenv("AWS_LAMBDA_FUNCTION_NAME", "foobar") - p = profiler.Profiler() - assert isinstance(p._scheduler, scheduler.ServerlessScheduler) - assert p.tags["functionname"] == "foobar" diff --git a/tests/profiling/test_scheduler.py b/tests/profiling/test_scheduler.py deleted file mode 100644 index 0da58e0e922..00000000000 --- a/tests/profiling/test_scheduler.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- encoding: utf-8 -*- -import logging -import time - -import mock - -from ddtrace.profiling import scheduler - - -def test_exporter_failure(): - s = scheduler.Scheduler() - s.flush() - - -def test_thread_name(): - s = scheduler.Scheduler() - s.start() - assert s._worker.name == "ddtrace.profiling.scheduler:Scheduler" - s.stop() - - -def test_before_flush(): - x = {} - - def call_me(): - x["OK"] = True - - s = scheduler.Scheduler(before_flush=call_me) - s.flush() - assert x["OK"] - - -def test_before_flush_failure(caplog): - def call_me(): - raise Exception("LOL") - - s = scheduler.Scheduler(before_flush=call_me) - s.flush() - assert caplog.record_tuples == [ - ("ddtrace.profiling.scheduler", logging.ERROR, "Scheduler before_flush hook failed") - ] - - -@mock.patch("ddtrace.profiling.scheduler.Scheduler.periodic") -def test_serverless_periodic(mock_periodic): - s = scheduler.ServerlessScheduler() - # Fake start() - s._last_export = time.time_ns() - s.periodic() - assert s._profiled_intervals == 1 - mock_periodic.assert_not_called() - s._last_export = time.time_ns() - 65 - s._profiled_intervals = 65 - s.periodic() - assert s._profiled_intervals == 0 - assert s.interval == 1 - mock_periodic.assert_called() diff --git a/tests/profiling/test_uwsgi.py b/tests/profiling/test_uwsgi.py deleted file mode 100644 index 79b20e917a8..00000000000 --- a/tests/profiling/test_uwsgi.py +++ /dev/null @@ -1,150 +0,0 @@ -# -*- encoding: utf-8 -*- -import os -import re -import signal -from subprocess import TimeoutExpired -import sys -import tempfile -import time - -import pytest - -from tests.contrib.uwsgi import run_uwsgi - -from . import utils - - -# uwsgi is not available on Windows -if sys.platform == "win32": - pytestmark = pytest.mark.skip - -THREADS_MSG = ( - b"ddtrace.internal.uwsgi.uWSGIConfigError: enable-threads option must be set to true, or a positive " - b"number of threads must be set" -) - -uwsgi_app = os.path.join(os.path.dirname(__file__), "uwsgi-app.py") - - -@pytest.fixture -def uwsgi(monkeypatch): - # Do not ignore profiler so we have samples in the output pprof - monkeypatch.setenv("DD_PROFILING_IGNORE_PROFILER", "0") - # Do not use pytest tmpdir fixtures which generate directories longer than allowed for a socket file name - socket_name = tempfile.mktemp() - cmd = ["uwsgi", "--need-app", "--die-on-term", "--socket", socket_name, "--wsgi-file", uwsgi_app] - - try: - yield run_uwsgi(cmd) - finally: - os.unlink(socket_name) - - -def test_uwsgi_threads_disabled(uwsgi): - proc = uwsgi() - stdout, _ = proc.communicate() - assert proc.wait() != 0 - assert THREADS_MSG in stdout - - -def test_uwsgi_threads_number_set(uwsgi): - proc = uwsgi("--threads", "1") - try: - stdout, _ = proc.communicate(timeout=1) - except TimeoutExpired: - proc.terminate() - stdout, _ = proc.communicate() - assert THREADS_MSG not in stdout - - -def test_uwsgi_threads_enabled(uwsgi, tmp_path, monkeypatch): - filename = str(tmp_path / "uwsgi.pprof") - monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) - proc = uwsgi("--enable-threads") - worker_pids = _get_worker_pids(proc.stdout, 1) - # Give some time to the process to actually startup - time.sleep(3) - proc.terminate() - assert proc.wait() == 30 - for pid in worker_pids: - utils.check_pprof_file("%s.%d" % (filename, pid)) - - -def test_uwsgi_threads_processes_no_master(uwsgi, monkeypatch): - proc = uwsgi("--enable-threads", "--processes", "2") - stdout, _ = proc.communicate() - assert ( - b"ddtrace.internal.uwsgi.uWSGIConfigError: master option must be enabled when multiple processes are used" - in stdout - ) - - -def _get_worker_pids(stdout, num_worker, num_app_started=1): - worker_pids = [] - started = 0 - while True: - line = stdout.readline() - if line == b"": - break - elif b"WSGI app 0 (mountpoint='') ready" in line: - started += 1 - else: - m = re.match(r"^spawned uWSGI worker \d+ .*\(pid: (\d+),", line.decode()) - if m: - worker_pids.append(int(m.group(1))) - - if len(worker_pids) == num_worker and num_app_started == started: - break - - return worker_pids - - -def test_uwsgi_threads_processes_master(uwsgi, tmp_path, monkeypatch): - filename = str(tmp_path / "uwsgi.pprof") - monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) - proc = uwsgi("--enable-threads", "--master", "--py-call-uwsgi-fork-hooks", "--processes", "2") - worker_pids = _get_worker_pids(proc.stdout, 2) - # Give some time to child to actually startup - time.sleep(3) - proc.terminate() - assert proc.wait() == 0 - for pid in worker_pids: - utils.check_pprof_file("%s.%d" % (filename, pid)) - - -def test_uwsgi_threads_processes_master_lazy_apps(uwsgi, tmp_path, monkeypatch): - filename = str(tmp_path / "uwsgi.pprof") - monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) - proc = uwsgi("--enable-threads", "--master", "--processes", "2", "--lazy-apps") - worker_pids = _get_worker_pids(proc.stdout, 2, 2) - # Give some time to child to actually startup - time.sleep(3) - proc.terminate() - assert proc.wait() == 0 - for pid in worker_pids: - utils.check_pprof_file("%s.%d" % (filename, pid)) - - -def test_uwsgi_threads_processes_no_master_lazy_apps(uwsgi, tmp_path, monkeypatch): - filename = str(tmp_path / "uwsgi.pprof") - monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) - proc = uwsgi("--enable-threads", "--processes", "2", "--lazy-apps") - worker_pids = _get_worker_pids(proc.stdout, 2, 2) - # Give some time to child to actually startup - time.sleep(3) - # The processes are started without a master/parent so killing one does not kill the other: - # Kill them all and wait until they die. - for pid in worker_pids: - os.kill(pid, signal.SIGTERM) - # The first worker is our child, we can wait for it "normally" - os.waitpid(worker_pids[0], 0) - # The other ones are grandchildren, we can't wait for it with `waitpid` - for pid in worker_pids[1:]: - # Wait for the uwsgi workers to all die - while True: - try: - os.kill(pid, 0) - except OSError: - break - for pid in worker_pids: - utils.check_pprof_file("%s.%d" % (filename, pid)) diff --git a/tests/profiling/utils.py b/tests/profiling/utils.py deleted file mode 100644 index f9a74ea1943..00000000000 --- a/tests/profiling/utils.py +++ /dev/null @@ -1,11 +0,0 @@ -from tests.profiling.collector import pprof_utils - - -def check_pprof_file( - filename, # type: str - sample_type="cpu-samples", -): - profile = pprof_utils.parse_newest_profile(filename) - - samples = pprof_utils.get_samples_with_value_type(profile, sample_type) - assert len(samples) >= 1 diff --git a/tests/profiling/_test_multiprocessing.py b/tests/profiling_v2/_test_multiprocessing.py similarity index 100% rename from tests/profiling/_test_multiprocessing.py rename to tests/profiling_v2/_test_multiprocessing.py diff --git a/tests/profiling_v2/collector/test_asyncio.py b/tests/profiling_v2/collector/test_asyncio.py index ecac824c943..4aeac5409d1 100644 --- a/tests/profiling_v2/collector/test_asyncio.py +++ b/tests/profiling_v2/collector/test_asyncio.py @@ -9,8 +9,8 @@ from ddtrace import ext from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import asyncio as collector_asyncio -from tests.profiling.collector import test_collector from tests.profiling_v2.collector import pprof_utils +from tests.profiling_v2.collector import test_collector from tests.profiling_v2.collector.lock_utils import get_lock_linenos from tests.profiling_v2.collector.lock_utils import init_linenos diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index f041904e6a9..baba54230c4 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -7,12 +7,12 @@ import uuid import pytest +from tests.profiling.collector import test_collector from ddtrace import ext from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import stack from tests.conftest import get_original_test_name -from tests.profiling.collector import test_collector from tests.profiling_v2.collector import pprof_utils diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling_v2/collector/test_threading.py index 6ce4b3010e0..791803360fc 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling_v2/collector/test_threading.py @@ -21,8 +21,8 @@ from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector.threading import ThreadingLockCollector from ddtrace.profiling.collector.threading import ThreadingRLockCollector -from tests.profiling.collector import test_collector from tests.profiling_v2.collector import pprof_utils +from tests.profiling_v2.collector import test_collector from tests.profiling_v2.collector.lock_utils import LineNo from tests.profiling_v2.collector.lock_utils import get_lock_linenos from tests.profiling_v2.collector.lock_utils import init_linenos diff --git a/tests/profiling/gevent_fork.py b/tests/profiling_v2/gevent_fork.py similarity index 100% rename from tests/profiling/gevent_fork.py rename to tests/profiling_v2/gevent_fork.py diff --git a/tests/profiling/gunicorn-app.py b/tests/profiling_v2/gunicorn-app.py similarity index 100% rename from tests/profiling/gunicorn-app.py rename to tests/profiling_v2/gunicorn-app.py diff --git a/tests/profiling/run.py b/tests/profiling_v2/run.py similarity index 100% rename from tests/profiling/run.py rename to tests/profiling_v2/run.py diff --git a/tests/profiling/suitespec.yml b/tests/profiling_v2/suitespec.yml similarity index 95% rename from tests/profiling/suitespec.yml rename to tests/profiling_v2/suitespec.yml index 51eb1cfe636..bc21136da4a 100644 --- a/tests/profiling/suitespec.yml +++ b/tests/profiling_v2/suitespec.yml @@ -81,13 +81,13 @@ suites: profile_v2: env: DD_TRACE_AGENT_URL: '' - # `riot list --hash-only profile-v2$ | wc -1` = 19 - parallelism: 16 + # `riot list --hash-only profile-v2 | wc -l` = 26 + parallelism: 26 paths: - '@bootstrap' - '@core' - '@profiling' - - tests/profiling/suitespec.yml + - tests/profiling_v2/suitespec.yml - tests/profiling_v2/* pattern: profile-v2 retry: 2 diff --git a/tests/profiling_v2/test_main.py b/tests/profiling_v2/test_main.py index debd37213fc..224ef941cc9 100644 --- a/tests/profiling_v2/test_main.py +++ b/tests/profiling_v2/test_main.py @@ -49,7 +49,7 @@ def test_call_script_pprof_output(tmp_path): stdout, stderr, exitcode, _ = call_program( "ddtrace-run", sys.executable, - os.path.join(os.path.dirname(__file__), "../profiling", "simple_program.py"), + os.path.join(os.path.dirname(__file__), "simple_program.py"), env=env, ) if sys.platform == "win32": @@ -139,7 +139,7 @@ def test_fork(tmp_path): def test_fork_gevent(): env = os.environ.copy() stdout, stderr, exitcode, pid = call_program( - "python", os.path.join(os.path.dirname(__file__), "../profiling", "gevent_fork.py"), env=env + "python", os.path.join(os.path.dirname(__file__), "gevent_fork.py"), env=env ) assert exitcode == 0 @@ -160,7 +160,7 @@ def test_multiprocessing(method, tmp_path): stdout, stderr, exitcode, _ = call_program( "ddtrace-run", sys.executable, - os.path.join(os.path.dirname(__file__), "../profiling", "_test_multiprocessing.py"), + os.path.join(os.path.dirname(__file__), "_test_multiprocessing.py"), method, env=env, ) diff --git a/tests/profiling_v2/test_scheduler.py b/tests/profiling_v2/test_scheduler.py index 323629d1c39..9d89a51574c 100644 --- a/tests/profiling_v2/test_scheduler.py +++ b/tests/profiling_v2/test_scheduler.py @@ -6,6 +6,11 @@ from ddtrace.profiling import scheduler +def test_exporter_failure(): + s = scheduler.Scheduler() + s.flush() + + def test_thread_name(): s = scheduler.Scheduler() s.start() diff --git a/tests/profiling/uwsgi-app.py b/tests/profiling_v2/uwsgi-app.py similarity index 100% rename from tests/profiling/uwsgi-app.py rename to tests/profiling_v2/uwsgi-app.py From 1c3f056b78db768176da7f70c39ea55c33ebc8a5 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 17 Nov 2025 16:27:49 -0500 Subject: [PATCH 05/11] fix riotfile --- riotfile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/riotfile.py b/riotfile.py index 11bef8d7862..8316774fbfe 100644 --- a/riotfile.py +++ b/riotfile.py @@ -3225,7 +3225,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="profile-v2", # NB riot commands that use this Venv must include --pass-env to work properly - command="python -m tests.profiling.run pytest -v --no-cov --capture=no --benchmark-disable --ignore='tests/profiling_v2/collector/test_memalloc.py' {cmdargs} tests/profiling_v2", # noqa: E501 + command="python -m tests.profiling_v2.run pytest -v --no-cov --capture=no --benchmark-disable --ignore='tests/profiling_v2/collector/test_memalloc.py' {cmdargs} tests/profiling_v2", # noqa: E501 env={ "DD_PROFILING_ENABLE_ASSERTS": "1", "CPUCOUNT": "12", @@ -3248,7 +3248,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT venvs=[ Venv( name="profile-v2-uwsgi", - command="python -m tests.profiling.run pytest -v --no-cov --capture=no --benchmark-disable {cmdargs} tests/profiling_v2/test_uwsgi.py", # noqa: E501 + command="python -m tests.profiling_v2.run pytest -v --no-cov --capture=no --benchmark-disable {cmdargs} tests/profiling_v2/test_uwsgi.py", # noqa: E501 pys=select_pys(max_version="3.13"), pkgs={ "uwsgi": "<2.0.30", @@ -3366,7 +3366,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="profile-v2-memalloc", - command="python -m tests.profiling.run pytest -v --no-cov --capture=no --benchmark-disable {cmdargs} tests/profiling_v2/collector/test_memalloc.py", # noqa: E501 + command="python -m tests.profiling_v2.run pytest -v --no-cov --capture=no --benchmark-disable {cmdargs} tests/profiling_v2/collector/test_memalloc.py", # noqa: E501 # skipping v3.14 for now due to an unstable `lz4 ` lib issue: https://gitlab.ddbuild.io/DataDog/apm-reliability/dd-trace-py/-/jobs/1163312347 pys=select_pys(max_version="3.13"), pkgs={ From 2af0c42a674360d4721b031c7de4496139095c90 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 17 Nov 2025 16:30:34 -0500 Subject: [PATCH 06/11] fix paths --- tests/profiling_v2/collector/test_stack.py | 4 ++-- tests/profiling_v2/test_gunicorn.py | 2 +- tests/profiling_v2/test_uwsgi.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index baba54230c4..1ae3f7002c6 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -7,7 +7,7 @@ import uuid import pytest -from tests.profiling.collector import test_collector +from tests.profiling_v2.collector import test_collector from ddtrace import ext from ddtrace.internal.datadog.profiling import ddup @@ -35,7 +35,7 @@ def test_collect_truncate(): import os - from tests.profiling.collector.test_stack import func1 + from tests.profiling_v2.collector.test_stack import func1 from ddtrace.profiling import profiler from tests.profiling_v2.collector import pprof_utils diff --git a/tests/profiling_v2/test_gunicorn.py b/tests/profiling_v2/test_gunicorn.py index 8d99a36207b..dc154d54f46 100644 --- a/tests/profiling_v2/test_gunicorn.py +++ b/tests/profiling_v2/test_gunicorn.py @@ -43,7 +43,7 @@ def _run_gunicorn(*args): os.path.dirname(__file__), ] + list(args) - + ["tests.profiling.gunicorn-app:app"] + + ["tests.profiling_v2.gunicorn-app:app"] ) return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) diff --git a/tests/profiling_v2/test_uwsgi.py b/tests/profiling_v2/test_uwsgi.py index c2bc3fd83ab..66d4910c44f 100644 --- a/tests/profiling_v2/test_uwsgi.py +++ b/tests/profiling_v2/test_uwsgi.py @@ -22,7 +22,7 @@ b"number of threads must be set" ) -uwsgi_app = os.path.join(os.path.dirname(__file__), "..", "profiling", "uwsgi-app.py") +uwsgi_app = os.path.join(os.path.dirname(__file__), "uwsgi-app.py") @pytest.fixture From c7530959541ac1c48a2ef656c1f74e891a223d7d Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 17 Nov 2025 16:37:35 -0500 Subject: [PATCH 07/11] update tests --- tests/profiling_v2/collector/test_stack.py | 101 +++++---------------- 1 file changed, 22 insertions(+), 79 deletions(-) diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index 1ae3f7002c6..c810c45c165 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -7,13 +7,13 @@ import uuid import pytest -from tests.profiling_v2.collector import test_collector from ddtrace import ext from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import stack from tests.conftest import get_original_test_name from tests.profiling_v2.collector import pprof_utils +from tests.profiling_v2.collector import test_collector # Python 3.11.9 is not compatible with gevent, https://github.com/gevent/gevent/issues/2040 @@ -25,6 +25,26 @@ ) +def func1(): + return func2() + + +def func2(): + return func3() + + +def func3(): + return func4() + + +def func4(): + return func5() + + +def func5(): + return time.sleep(1) + + # Use subprocess as ddup config persists across tests. @pytest.mark.subprocess( env=dict( @@ -35,10 +55,9 @@ def test_collect_truncate(): import os - from tests.profiling_v2.collector.test_stack import func1 - from ddtrace.profiling import profiler from tests.profiling_v2.collector import pprof_utils + from tests.profiling_v2.collector.test_stack import func1 pprof_prefix = os.environ["DD_PROFILING_OUTPUT_PPROF"] output_filename = pprof_prefix + "." + str(os.getpid()) @@ -830,79 +849,3 @@ def _trace(): for t in threads: t.join() - - -@pytest.mark.skipif(not TESTING_GEVENT or sys.version_info < (3, 9), reason="Not testing gevent") -@pytest.mark.subprocess(ddtrace_run=True) -def test_collect_gevent_threads(): - import gevent.monkey - - gevent.monkey.patch_all() - - import os - import threading - import time - - from ddtrace.internal.datadog.profiling import ddup - from ddtrace.profiling.collector import stack - from tests.profiling_v2.collector import pprof_utils - - iteration = 100 - sleep_time = 0.01 - nb_threads = 15 - - # Start some greenthreads: they do nothing we just keep switching between them. - def _nothing(): - for _ in range(iteration): - # Do nothing and just switch to another greenlet - time.sleep(sleep_time) - - test_name = "test_collect_gevent_threads" - pprof_prefix = "/tmp/" + test_name - output_filename = pprof_prefix + "." + str(os.getpid()) - - assert ddup.is_available - ddup.config(env="test", service="test_collect_gevent_threads", version="my_version", output_filename=pprof_prefix) - ddup.start() - - with stack.StackCollector(): - threads = [] - i_to_tid = {} - for i in range(nb_threads): - t = threading.Thread(target=_nothing, name="TestThread %d" % i) - i_to_tid[i] = t.ident - t.start() - threads.append(t) - for t in threads: - t.join() - - ddup.upload() - - profile = pprof_utils.parse_newest_profile(output_filename) - samples = pprof_utils.get_samples_with_label_key(profile, "task name") - assert len(samples) > 0 - - for task_id in range(nb_threads): - pprof_utils.assert_profile_has_sample( - profile, - samples, - expected_sample=pprof_utils.StackEvent( - task_name="TestThread %d" % task_id, - task_id=i_to_tid[task_id], - thread_id=i_to_tid[task_id], - locations=[ - pprof_utils.StackLocation( - filename="test_stack.py", - function_name="_nothing", - line_no=_nothing.__code__.co_firstlineno + 3, - ) - ], - ), - ) - pprof_utils.assert_profile_has_sample( - profile, - samples, - expected_sample=pprof_utils.StackEvent( - task_name="MainThread", - ), - ) From 7757e8173a5ac445a5a7b3d43137dc850257dfce Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 17 Nov 2025 16:45:07 -0500 Subject: [PATCH 08/11] refer to profile --- scripts/needs_testrun.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/needs_testrun.py b/scripts/needs_testrun.py index c168f843e9c..0ee09cc7631 100755 --- a/scripts/needs_testrun.py +++ b/scripts/needs_testrun.py @@ -151,7 +151,7 @@ def needs_testrun(suite: str, pr_number: int, sha: t.Optional[str] = None) -> bo ... needs_testrun("debugger", 6485) ... needs_testrun("debugger", 6388) ... needs_testrun("foobar", 6412) - ... needs_testrun("profiling::profile_v2", 11690) + ... needs_testrun("profiling::profile", 11690) True True True From 020ab8cf9c57315da10eea87e624dafa6222bff7 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 17 Nov 2025 16:51:59 -0500 Subject: [PATCH 09/11] update --- scripts/needs_testrun.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/needs_testrun.py b/scripts/needs_testrun.py index 0ee09cc7631..25cdbe29ddf 100755 --- a/scripts/needs_testrun.py +++ b/scripts/needs_testrun.py @@ -129,8 +129,11 @@ def get_changed_files(pr_number: int, sha: t.Optional[str] = None) -> t.Set[str] for page in count(1): result = {_["filename"] for _ in github_api(f"/pulls/{pr_number}/files", {"page": page})} if not result: + # End of pagination, return collected files return files files |= result + # If we got here, we successfully collected files from all pages + return files except Exception: LOGGER.warning("Failed to get changed files from GitHub API", exc_info=True) @@ -151,7 +154,7 @@ def needs_testrun(suite: str, pr_number: int, sha: t.Optional[str] = None) -> bo ... needs_testrun("debugger", 6485) ... needs_testrun("debugger", 6388) ... needs_testrun("foobar", 6412) - ... needs_testrun("profiling::profile", 11690) + ... needs_testrun("profiling_v2::profile_v2", 11690) True True True From d748a341931fb13df8e7bfa12126d6ec0d5098d7 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 17 Nov 2025 16:53:43 -0500 Subject: [PATCH 10/11] remove comments --- scripts/needs_testrun.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/needs_testrun.py b/scripts/needs_testrun.py index 25cdbe29ddf..78463bab78b 100755 --- a/scripts/needs_testrun.py +++ b/scripts/needs_testrun.py @@ -129,11 +129,8 @@ def get_changed_files(pr_number: int, sha: t.Optional[str] = None) -> t.Set[str] for page in count(1): result = {_["filename"] for _ in github_api(f"/pulls/{pr_number}/files", {"page": page})} if not result: - # End of pagination, return collected files return files files |= result - # If we got here, we successfully collected files from all pages - return files except Exception: LOGGER.warning("Failed to get changed files from GitHub API", exc_info=True) From dbcdc56ff58a524012c6e7e4311408a8020b3d8e Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Tue, 18 Nov 2025 12:00:03 -0500 Subject: [PATCH 11/11] remove profiling_v2 --- .github/CODEOWNERS | 1 - hatch.toml | 2 +- mypy.ini | 8 +++---- pyproject.toml | 4 ++-- riotfile.py | 6 ++--- scripts/needs_testrun.py | 2 +- sgconfig.yml | 2 +- tests/README.md | 1 - tests/{profiling_v2 => profiling}/__init__.py | 0 .../_test_multiprocessing.py | 0 .../collector/conftest.py | 0 .../collector/lock_utils.py | 0 .../collector/pprof.proto | 0 .../collector/pprof_312_pb2.py | 0 .../collector/pprof_319_pb2.py | 0 .../collector/pprof_3_pb2.py | 0 .../collector/pprof_421_pb2.py | 0 .../collector/pprof_utils.py | 6 ++--- .../collector/test_asyncio.py | 8 +++---- .../collector/test_collector.py | 0 .../collector/test_memalloc.py | 8 +++---- .../collector/test_sample_count.py | 0 .../collector/test_stack.py | 12 +++++----- .../collector/test_stack_asyncio.py | 14 +++++------ .../collector/test_task.py | 0 .../collector/test_threading.py | 24 +++++++++---------- .../collector/test_threading_asyncio.py | 6 ++--- .../collector/test_traceback.py | 0 .../exporter/__init__.py | 0 .../exporter/test_ddup.py | 0 .../gevent_fork.py | 0 .../gunicorn-app.py | 0 .../gunicorn.conf.py | 0 .../{profiling_v2 => profiling}/native_tests | 0 tests/{profiling_v2 => profiling}/run.py | 0 .../simple_program.py | 0 .../simple_program_fork.py | 0 .../simple_program_gevent.py | 0 .../simple_program_pytorch_gpu.py | 0 .../{profiling_v2 => profiling}/suitespec.yml | 4 ++-- .../test_accuracy.py | 6 ++--- .../test_code_provenance.py | 0 .../test_gunicorn.py | 4 ++-- .../{profiling_v2 => profiling}/test_main.py | 4 ++-- .../test_profiler.py | 0 .../test_pytorch.py | 2 +- .../test_scheduler.py | 0 .../{profiling_v2 => profiling}/test_uwsgi.py | 2 +- .../{profiling_v2 => profiling}/uwsgi-app.py | 0 49 files changed, 62 insertions(+), 64 deletions(-) rename tests/{profiling_v2 => profiling}/__init__.py (100%) rename tests/{profiling_v2 => profiling}/_test_multiprocessing.py (100%) rename tests/{profiling_v2 => profiling}/collector/conftest.py (100%) rename tests/{profiling_v2 => profiling}/collector/lock_utils.py (100%) rename tests/{profiling_v2 => profiling}/collector/pprof.proto (100%) rename tests/{profiling_v2 => profiling}/collector/pprof_312_pb2.py (100%) rename tests/{profiling_v2 => profiling}/collector/pprof_319_pb2.py (100%) rename tests/{profiling_v2 => profiling}/collector/pprof_3_pb2.py (100%) rename tests/{profiling_v2 => profiling}/collector/pprof_421_pb2.py (100%) rename tests/{profiling_v2 => profiling}/collector/pprof_utils.py (98%) rename tests/{profiling_v2 => profiling}/collector/test_asyncio.py (96%) rename tests/{profiling_v2 => profiling}/collector/test_collector.py (100%) rename tests/{profiling_v2 => profiling}/collector/test_memalloc.py (99%) rename tests/{profiling_v2 => profiling}/collector/test_sample_count.py (100%) rename tests/{profiling_v2 => profiling}/collector/test_stack.py (98%) rename tests/{profiling_v2 => profiling}/collector/test_stack_asyncio.py (98%) rename tests/{profiling_v2 => profiling}/collector/test_task.py (100%) rename tests/{profiling_v2 => profiling}/collector/test_threading.py (98%) rename tests/{profiling_v2 => profiling}/collector/test_threading_asyncio.py (93%) rename tests/{profiling_v2 => profiling}/collector/test_traceback.py (100%) rename tests/{profiling_v2 => profiling}/exporter/__init__.py (100%) rename tests/{profiling_v2 => profiling}/exporter/test_ddup.py (100%) rename tests/{profiling_v2 => profiling}/gevent_fork.py (100%) rename tests/{profiling_v2 => profiling}/gunicorn-app.py (100%) rename tests/{profiling_v2 => profiling}/gunicorn.conf.py (100%) rename tests/{profiling_v2 => profiling}/native_tests (100%) rename tests/{profiling_v2 => profiling}/run.py (100%) rename tests/{profiling_v2 => profiling}/simple_program.py (100%) rename tests/{profiling_v2 => profiling}/simple_program_fork.py (100%) rename tests/{profiling_v2 => profiling}/simple_program_gevent.py (100%) rename tests/{profiling_v2 => profiling}/simple_program_pytorch_gpu.py (100%) rename tests/{profiling_v2 => profiling}/suitespec.yml (97%) rename tests/{profiling_v2 => profiling}/test_accuracy.py (93%) rename tests/{profiling_v2 => profiling}/test_code_provenance.py (100%) rename tests/{profiling_v2 => profiling}/test_gunicorn.py (97%) rename tests/{profiling_v2 => profiling}/test_main.py (98%) rename tests/{profiling_v2 => profiling}/test_profiler.py (100%) rename tests/{profiling_v2 => profiling}/test_pytorch.py (96%) rename tests/{profiling_v2 => profiling}/test_scheduler.py (100%) rename tests/{profiling_v2 => profiling}/test_uwsgi.py (99%) rename tests/{profiling_v2 => profiling}/uwsgi-app.py (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fa5f5d73f3f..b4bc1befb29 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -137,7 +137,6 @@ ddtrace/profiling @DataDog/profiling-python ddtrace/internal/settings/profiling.py @DataDog/profiling-python ddtrace/internal/datadog/profiling @DataDog/profiling-python tests/profiling @DataDog/profiling-python -tests/profiling_v2 @DataDog/profiling-python .gitlab/tests/profiling.yml @DataDog/profiling-python # MLObs diff --git a/hatch.toml b/hatch.toml index 3a7815ce8b8..fe02a4188ea 100644 --- a/hatch.toml +++ b/hatch.toml @@ -159,7 +159,7 @@ DD_PROFILING_PYTORCH_ENABLED = "true" test = [ "uname -a", "pip freeze", - "python -m pytest tests/profiling_v2/test_pytorch.py -vvv --capture=tee-sys", + "python -m pytest tests/profiling/test_pytorch.py -vvv --capture=tee-sys", ] [[envs.profiling_pytorch.matrix]] diff --git a/mypy.ini b/mypy.ini index 5179adb6163..882598d8256 100644 --- a/mypy.ini +++ b/mypy.ini @@ -17,16 +17,16 @@ ignore_errors = true [mypy-ddtrace.vendor.*] ignore_errors = true -[mypy-tests.profiling_v2.collector.pprof_3_pb2] +[mypy-tests.profiling.collector.pprof_3_pb2] ignore_errors = true -[mypy-tests.profiling_v2.collector.pprof_312_pb2] +[mypy-tests.profiling.collector.pprof_312_pb2] ignore_errors = true -[mypy-tests.profiling_v2.collector.pprof_319_pb2] +[mypy-tests.profiling.collector.pprof_319_pb2] ignore_errors = true -[mypy-tests.profiling_v2.collector.pprof_421_pb2] +[mypy-tests.profiling.collector.pprof_421_pb2] ignore_errors = true [mypy-ddtrace.internal.datadog.profiling.ddup] diff --git a/pyproject.toml b/pyproject.toml index ea4e742f98b..0b9e1973ff4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,7 +136,7 @@ exclude = ''' | build/ | dist/ | tests/lib-injection/dd-lib-python-init-test-protobuf-old/addressbook_pb2.py$ - | tests/profiling_v2/collector/pprof_.*_pb2.py$ + | tests/profiling/collector/pprof_.*_pb2.py$ ) ''' @@ -186,7 +186,7 @@ exclude = [ "ddtrace/__init__.py", "ddtrace/vendor/*", "ddtrace/appsec/_iast/_taint_tracking/_vendor/*", - "tests/profiling_v2/collector/pprof_*pb2.py", + "tests/profiling/collector/pprof_*pb2.py", "tests/contrib/grpc/hello_pb2.py", "tests/contrib/django_celery/app/*", "tests/contrib/protobuf/schemas/**/*.py", diff --git a/riotfile.py b/riotfile.py index 704a7d13e52..4cbbb33d267 100644 --- a/riotfile.py +++ b/riotfile.py @@ -3235,7 +3235,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="profile-v2", # NB riot commands that use this Venv must include --pass-env to work properly - command="python -m tests.profiling_v2.run pytest -v --no-cov --capture=no --benchmark-disable --ignore='tests/profiling_v2/collector/test_memalloc.py' {cmdargs} tests/profiling_v2", # noqa: E501 + command="python -m tests.profiling.run pytest -v --no-cov --capture=no --benchmark-disable --ignore='tests/profiling/collector/test_memalloc.py' {cmdargs} tests/profiling", # noqa: E501 env={ "DD_PROFILING_ENABLE_ASSERTS": "1", "CPUCOUNT": "12", @@ -3258,7 +3258,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT venvs=[ Venv( name="profile-v2-uwsgi", - command="python -m tests.profiling_v2.run pytest -v --no-cov --capture=no --benchmark-disable {cmdargs} tests/profiling_v2/test_uwsgi.py", # noqa: E501 + command="python -m tests.profiling.run pytest -v --no-cov --capture=no --benchmark-disable {cmdargs} tests/profiling/test_uwsgi.py", # noqa: E501 pys=select_pys(max_version="3.13"), pkgs={ "uwsgi": "<2.0.30", @@ -3376,7 +3376,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="profile-v2-memalloc", - command="python -m tests.profiling_v2.run pytest -v --no-cov --capture=no --benchmark-disable {cmdargs} tests/profiling_v2/collector/test_memalloc.py", # noqa: E501 + command="python -m tests.profiling.run pytest -v --no-cov --capture=no --benchmark-disable {cmdargs} tests/profiling/collector/test_memalloc.py", # noqa: E501 # skipping v3.14 for now due to an unstable `lz4 ` lib issue: https://gitlab.ddbuild.io/DataDog/apm-reliability/dd-trace-py/-/jobs/1163312347 pys=select_pys(max_version="3.13"), pkgs={ diff --git a/scripts/needs_testrun.py b/scripts/needs_testrun.py index 78463bab78b..c168f843e9c 100755 --- a/scripts/needs_testrun.py +++ b/scripts/needs_testrun.py @@ -151,7 +151,7 @@ def needs_testrun(suite: str, pr_number: int, sha: t.Optional[str] = None) -> bo ... needs_testrun("debugger", 6485) ... needs_testrun("debugger", 6388) ... needs_testrun("foobar", 6412) - ... needs_testrun("profiling_v2::profile_v2", 11690) + ... needs_testrun("profiling::profile_v2", 11690) True True True diff --git a/sgconfig.yml b/sgconfig.yml index 0896551d707..f39c95bb6de 100644 --- a/sgconfig.yml +++ b/sgconfig.yml @@ -58,7 +58,7 @@ ignoreFiles: - test-results/**/* # Generated protobuf files - - "tests/profiling_v2/collector/pprof_*pb2.py" + - "tests/profiling/collector/pprof_*pb2.py" - "tests/contrib/grpc/hello_pb2.py" - "tests/lib-injection/dd-lib-python-init-test-protobuf-old/addressbook_pb2.py" diff --git a/tests/README.md b/tests/README.md index 8dcf61e07f3..3d002f0856e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -63,7 +63,6 @@ suites: - '@core' - '@profiling' - tests/profiling/* - - tests/profiling_v2/* services: - redis ``` diff --git a/tests/profiling_v2/__init__.py b/tests/profiling/__init__.py similarity index 100% rename from tests/profiling_v2/__init__.py rename to tests/profiling/__init__.py diff --git a/tests/profiling_v2/_test_multiprocessing.py b/tests/profiling/_test_multiprocessing.py similarity index 100% rename from tests/profiling_v2/_test_multiprocessing.py rename to tests/profiling/_test_multiprocessing.py diff --git a/tests/profiling_v2/collector/conftest.py b/tests/profiling/collector/conftest.py similarity index 100% rename from tests/profiling_v2/collector/conftest.py rename to tests/profiling/collector/conftest.py diff --git a/tests/profiling_v2/collector/lock_utils.py b/tests/profiling/collector/lock_utils.py similarity index 100% rename from tests/profiling_v2/collector/lock_utils.py rename to tests/profiling/collector/lock_utils.py diff --git a/tests/profiling_v2/collector/pprof.proto b/tests/profiling/collector/pprof.proto similarity index 100% rename from tests/profiling_v2/collector/pprof.proto rename to tests/profiling/collector/pprof.proto diff --git a/tests/profiling_v2/collector/pprof_312_pb2.py b/tests/profiling/collector/pprof_312_pb2.py similarity index 100% rename from tests/profiling_v2/collector/pprof_312_pb2.py rename to tests/profiling/collector/pprof_312_pb2.py diff --git a/tests/profiling_v2/collector/pprof_319_pb2.py b/tests/profiling/collector/pprof_319_pb2.py similarity index 100% rename from tests/profiling_v2/collector/pprof_319_pb2.py rename to tests/profiling/collector/pprof_319_pb2.py diff --git a/tests/profiling_v2/collector/pprof_3_pb2.py b/tests/profiling/collector/pprof_3_pb2.py similarity index 100% rename from tests/profiling_v2/collector/pprof_3_pb2.py rename to tests/profiling/collector/pprof_3_pb2.py diff --git a/tests/profiling_v2/collector/pprof_421_pb2.py b/tests/profiling/collector/pprof_421_pb2.py similarity index 100% rename from tests/profiling_v2/collector/pprof_421_pb2.py rename to tests/profiling/collector/pprof_421_pb2.py diff --git a/tests/profiling_v2/collector/pprof_utils.py b/tests/profiling/collector/pprof_utils.py similarity index 98% rename from tests/profiling_v2/collector/pprof_utils.py rename to tests/profiling/collector/pprof_utils.py index 3946b8d02f3..f6a2a4de6c4 100644 --- a/tests/profiling_v2/collector/pprof_utils.py +++ b/tests/profiling/collector/pprof_utils.py @@ -12,7 +12,7 @@ import zstandard as zstd -from tests.profiling_v2.collector.lock_utils import LineNo +from tests.profiling.collector.lock_utils import LineNo UINT64_MAX = (1 << 64) - 1 @@ -34,12 +34,12 @@ def _protobuf_version() -> Tuple[int, int, int]: if _pb_version >= v: import sys - pprof_module = "tests.profiling_v2.collector.pprof_%s%s_pb2" % v + pprof_module = "tests.profiling.collector.pprof_%s%s_pb2" % v __import__(pprof_module) pprof_pb2 = sys.modules[pprof_module] break else: - from tests.profiling_v2.collector import pprof_3_pb2 as pprof_pb2 # type: ignore[no-redef] + from tests.profiling.collector import pprof_3_pb2 as pprof_pb2 # type: ignore[no-redef] # Clamp the value to the range [0, UINT64_MAX] as done in clamp_to_uint64_unsigned diff --git a/tests/profiling_v2/collector/test_asyncio.py b/tests/profiling/collector/test_asyncio.py similarity index 96% rename from tests/profiling_v2/collector/test_asyncio.py rename to tests/profiling/collector/test_asyncio.py index 4aeac5409d1..c427a20f662 100644 --- a/tests/profiling_v2/collector/test_asyncio.py +++ b/tests/profiling/collector/test_asyncio.py @@ -9,10 +9,10 @@ from ddtrace import ext from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import asyncio as collector_asyncio -from tests.profiling_v2.collector import pprof_utils -from tests.profiling_v2.collector import test_collector -from tests.profiling_v2.collector.lock_utils import get_lock_linenos -from tests.profiling_v2.collector.lock_utils import init_linenos +from tests.profiling.collector import pprof_utils +from tests.profiling.collector import test_collector +from tests.profiling.collector.lock_utils import get_lock_linenos +from tests.profiling.collector.lock_utils import init_linenos init_linenos(__file__) diff --git a/tests/profiling_v2/collector/test_collector.py b/tests/profiling/collector/test_collector.py similarity index 100% rename from tests/profiling_v2/collector/test_collector.py rename to tests/profiling/collector/test_collector.py diff --git a/tests/profiling_v2/collector/test_memalloc.py b/tests/profiling/collector/test_memalloc.py similarity index 99% rename from tests/profiling_v2/collector/test_memalloc.py rename to tests/profiling/collector/test_memalloc.py index 2870932d7a4..c0811575f93 100644 --- a/tests/profiling_v2/collector/test_memalloc.py +++ b/tests/profiling/collector/test_memalloc.py @@ -9,7 +9,7 @@ from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import memalloc from ddtrace.profiling.event import DDFrame -from tests.profiling_v2.collector import pprof_utils +from tests.profiling.collector import pprof_utils PY_313_OR_ABOVE = sys.version_info[:2] >= (3, 13) @@ -30,8 +30,8 @@ def test_heap_samples_collected(): import os from ddtrace.profiling import Profiler - from tests.profiling_v2.collector import pprof_utils - from tests.profiling_v2.collector.test_memalloc import _allocate_1k + from tests.profiling.collector import pprof_utils + from tests.profiling.collector.test_memalloc import _allocate_1k # Test for https://github.com/DataDog/dd-trace-py/issues/11069 pprof_prefix = os.environ["DD_PROFILING_OUTPUT_PPROF"] @@ -145,7 +145,7 @@ def test_heap_profiler_large_heap_overhead(): # Un-skip this test if/when we improve the worst-case performance of the # heap profiler for large heaps from ddtrace.profiling import Profiler - from tests.profiling_v2.collector.test_memalloc import one + from tests.profiling.collector.test_memalloc import one p = Profiler() p.start() diff --git a/tests/profiling_v2/collector/test_sample_count.py b/tests/profiling/collector/test_sample_count.py similarity index 100% rename from tests/profiling_v2/collector/test_sample_count.py rename to tests/profiling/collector/test_sample_count.py diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling/collector/test_stack.py similarity index 98% rename from tests/profiling_v2/collector/test_stack.py rename to tests/profiling/collector/test_stack.py index c810c45c165..c68a85dfb49 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling/collector/test_stack.py @@ -12,8 +12,8 @@ from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import stack from tests.conftest import get_original_test_name -from tests.profiling_v2.collector import pprof_utils -from tests.profiling_v2.collector import test_collector +from tests.profiling.collector import pprof_utils +from tests.profiling.collector import test_collector # Python 3.11.9 is not compatible with gevent, https://github.com/gevent/gevent/issues/2040 @@ -56,8 +56,8 @@ def test_collect_truncate(): import os from ddtrace.profiling import profiler - from tests.profiling_v2.collector import pprof_utils - from tests.profiling_v2.collector.test_stack import func1 + from tests.profiling.collector import pprof_utils + from tests.profiling.collector.test_stack import func1 pprof_prefix = os.environ["DD_PROFILING_OUTPUT_PPROF"] output_filename = pprof_prefix + "." + str(os.getpid()) @@ -520,8 +520,8 @@ def test_collect_gevent_thread_task(): from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import stack - from tests.profiling_v2.collector import pprof_utils - from tests.profiling_v2.collector.test_stack import _fib + from tests.profiling.collector import pprof_utils + from tests.profiling.collector.test_stack import _fib test_name = "test_collect_gevent_thread_task" pprof_prefix = "/tmp/" + test_name diff --git a/tests/profiling_v2/collector/test_stack_asyncio.py b/tests/profiling/collector/test_stack_asyncio.py similarity index 98% rename from tests/profiling_v2/collector/test_stack_asyncio.py rename to tests/profiling/collector/test_stack_asyncio.py index 3c231cd5d28..a13ea2aaad9 100644 --- a/tests/profiling_v2/collector/test_stack_asyncio.py +++ b/tests/profiling/collector/test_stack_asyncio.py @@ -18,7 +18,7 @@ def test_asyncio(): from ddtrace.internal.datadog.profiling import stack_v2 from ddtrace.profiling import profiler from ddtrace.trace import tracer - from tests.profiling_v2.collector import pprof_utils + from tests.profiling.collector import pprof_utils assert stack_v2.is_available, stack_v2.failure_msg @@ -188,7 +188,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling_v2.collector import pprof_utils + from tests.profiling.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) @@ -312,7 +312,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling_v2.collector import pprof_utils + from tests.profiling.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) @@ -437,7 +437,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling_v2.collector import pprof_utils + from tests.profiling.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) @@ -562,7 +562,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling_v2.collector import pprof_utils + from tests.profiling.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) @@ -689,7 +689,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling_v2.collector import pprof_utils + from tests.profiling.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) @@ -816,7 +816,7 @@ async def main_task(): assert t1_name == "tracked 1" assert t2_name == "tracked 2" - from tests.profiling_v2.collector import pprof_utils + from tests.profiling.collector import pprof_utils output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_newest_profile(output_filename) diff --git a/tests/profiling_v2/collector/test_task.py b/tests/profiling/collector/test_task.py similarity index 100% rename from tests/profiling_v2/collector/test_task.py rename to tests/profiling/collector/test_task.py diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling/collector/test_threading.py similarity index 98% rename from tests/profiling_v2/collector/test_threading.py rename to tests/profiling/collector/test_threading.py index 791803360fc..fb4e0ad71cc 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling/collector/test_threading.py @@ -21,12 +21,12 @@ from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector.threading import ThreadingLockCollector from ddtrace.profiling.collector.threading import ThreadingRLockCollector -from tests.profiling_v2.collector import pprof_utils -from tests.profiling_v2.collector import test_collector -from tests.profiling_v2.collector.lock_utils import LineNo -from tests.profiling_v2.collector.lock_utils import get_lock_linenos -from tests.profiling_v2.collector.lock_utils import init_linenos -from tests.profiling_v2.collector.pprof_utils import pprof_pb2 +from tests.profiling.collector import pprof_utils +from tests.profiling.collector import test_collector +from tests.profiling.collector.lock_utils import LineNo +from tests.profiling.collector.lock_utils import get_lock_linenos +from tests.profiling.collector.lock_utils import init_linenos +from tests.profiling.collector.pprof_utils import pprof_pb2 # Type aliases for supported classes @@ -205,9 +205,9 @@ def test_lock_gevent_tasks() -> None: from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector.threading import ThreadingLockCollector - from tests.profiling_v2.collector import pprof_utils - from tests.profiling_v2.collector.lock_utils import get_lock_linenos - from tests.profiling_v2.collector.lock_utils import init_linenos + from tests.profiling.collector import pprof_utils + from tests.profiling.collector.lock_utils import get_lock_linenos + from tests.profiling.collector.lock_utils import init_linenos assert ddup.is_available, "ddup is not available" @@ -297,9 +297,9 @@ def test_rlock_gevent_tasks() -> None: from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector.threading import ThreadingRLockCollector - from tests.profiling_v2.collector import pprof_utils - from tests.profiling_v2.collector.lock_utils import get_lock_linenos - from tests.profiling_v2.collector.lock_utils import init_linenos + from tests.profiling.collector import pprof_utils + from tests.profiling.collector.lock_utils import get_lock_linenos + from tests.profiling.collector.lock_utils import init_linenos assert ddup.is_available, "ddup is not available" diff --git a/tests/profiling_v2/collector/test_threading_asyncio.py b/tests/profiling/collector/test_threading_asyncio.py similarity index 93% rename from tests/profiling_v2/collector/test_threading_asyncio.py rename to tests/profiling/collector/test_threading_asyncio.py index 89d22e2aa66..8c09d6a348d 100644 --- a/tests/profiling_v2/collector/test_threading_asyncio.py +++ b/tests/profiling/collector/test_threading_asyncio.py @@ -15,9 +15,9 @@ def test_lock_acquire_events(): import threading from ddtrace.profiling import profiler - from tests.profiling_v2.collector import pprof_utils - from tests.profiling_v2.collector.lock_utils import get_lock_linenos - from tests.profiling_v2.collector.lock_utils import init_linenos + from tests.profiling.collector import pprof_utils + from tests.profiling.collector.lock_utils import get_lock_linenos + from tests.profiling.collector.lock_utils import init_linenos init_linenos(os.environ["DD_PROFILING_FILE_PATH"]) diff --git a/tests/profiling_v2/collector/test_traceback.py b/tests/profiling/collector/test_traceback.py similarity index 100% rename from tests/profiling_v2/collector/test_traceback.py rename to tests/profiling/collector/test_traceback.py diff --git a/tests/profiling_v2/exporter/__init__.py b/tests/profiling/exporter/__init__.py similarity index 100% rename from tests/profiling_v2/exporter/__init__.py rename to tests/profiling/exporter/__init__.py diff --git a/tests/profiling_v2/exporter/test_ddup.py b/tests/profiling/exporter/test_ddup.py similarity index 100% rename from tests/profiling_v2/exporter/test_ddup.py rename to tests/profiling/exporter/test_ddup.py diff --git a/tests/profiling_v2/gevent_fork.py b/tests/profiling/gevent_fork.py similarity index 100% rename from tests/profiling_v2/gevent_fork.py rename to tests/profiling/gevent_fork.py diff --git a/tests/profiling_v2/gunicorn-app.py b/tests/profiling/gunicorn-app.py similarity index 100% rename from tests/profiling_v2/gunicorn-app.py rename to tests/profiling/gunicorn-app.py diff --git a/tests/profiling_v2/gunicorn.conf.py b/tests/profiling/gunicorn.conf.py similarity index 100% rename from tests/profiling_v2/gunicorn.conf.py rename to tests/profiling/gunicorn.conf.py diff --git a/tests/profiling_v2/native_tests b/tests/profiling/native_tests similarity index 100% rename from tests/profiling_v2/native_tests rename to tests/profiling/native_tests diff --git a/tests/profiling_v2/run.py b/tests/profiling/run.py similarity index 100% rename from tests/profiling_v2/run.py rename to tests/profiling/run.py diff --git a/tests/profiling_v2/simple_program.py b/tests/profiling/simple_program.py similarity index 100% rename from tests/profiling_v2/simple_program.py rename to tests/profiling/simple_program.py diff --git a/tests/profiling_v2/simple_program_fork.py b/tests/profiling/simple_program_fork.py similarity index 100% rename from tests/profiling_v2/simple_program_fork.py rename to tests/profiling/simple_program_fork.py diff --git a/tests/profiling_v2/simple_program_gevent.py b/tests/profiling/simple_program_gevent.py similarity index 100% rename from tests/profiling_v2/simple_program_gevent.py rename to tests/profiling/simple_program_gevent.py diff --git a/tests/profiling_v2/simple_program_pytorch_gpu.py b/tests/profiling/simple_program_pytorch_gpu.py similarity index 100% rename from tests/profiling_v2/simple_program_pytorch_gpu.py rename to tests/profiling/simple_program_pytorch_gpu.py diff --git a/tests/profiling_v2/suitespec.yml b/tests/profiling/suitespec.yml similarity index 97% rename from tests/profiling_v2/suitespec.yml rename to tests/profiling/suitespec.yml index bc21136da4a..1b0ca074891 100644 --- a/tests/profiling_v2/suitespec.yml +++ b/tests/profiling/suitespec.yml @@ -87,8 +87,8 @@ suites: - '@bootstrap' - '@core' - '@profiling' - - tests/profiling_v2/suitespec.yml - - tests/profiling_v2/* + - tests/profiling/suitespec.yml + - tests/profiling/* pattern: profile-v2 retry: 2 runner: riot diff --git a/tests/profiling_v2/test_accuracy.py b/tests/profiling/test_accuracy.py similarity index 93% rename from tests/profiling_v2/test_accuracy.py rename to tests/profiling/test_accuracy.py index 3507b788ae9..6fd014af181 100644 --- a/tests/profiling_v2/test_accuracy.py +++ b/tests/profiling/test_accuracy.py @@ -67,9 +67,9 @@ def test_accuracy_stack_v2(): import os from ddtrace.profiling import profiler - from tests.profiling_v2.collector import pprof_utils - from tests.profiling_v2.test_accuracy import assert_almost_equal - from tests.profiling_v2.test_accuracy import spend_16 + from tests.profiling.collector import pprof_utils + from tests.profiling.test_accuracy import assert_almost_equal + from tests.profiling.test_accuracy import spend_16 # Set this to 100 so we don't sleep too often and mess with the precision. p = profiler.Profiler() diff --git a/tests/profiling_v2/test_code_provenance.py b/tests/profiling/test_code_provenance.py similarity index 100% rename from tests/profiling_v2/test_code_provenance.py rename to tests/profiling/test_code_provenance.py diff --git a/tests/profiling_v2/test_gunicorn.py b/tests/profiling/test_gunicorn.py similarity index 97% rename from tests/profiling_v2/test_gunicorn.py rename to tests/profiling/test_gunicorn.py index dc154d54f46..78297c85e55 100644 --- a/tests/profiling_v2/test_gunicorn.py +++ b/tests/profiling/test_gunicorn.py @@ -8,7 +8,7 @@ import pytest -from tests.profiling_v2.collector import pprof_utils +from tests.profiling.collector import pprof_utils # DEV: gunicorn tests are hard to debug, so keeping these print statements for @@ -43,7 +43,7 @@ def _run_gunicorn(*args): os.path.dirname(__file__), ] + list(args) - + ["tests.profiling_v2.gunicorn-app:app"] + + ["tests.profiling.gunicorn-app:app"] ) return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) diff --git a/tests/profiling_v2/test_main.py b/tests/profiling/test_main.py similarity index 98% rename from tests/profiling_v2/test_main.py rename to tests/profiling/test_main.py index 224ef941cc9..343de097662 100644 --- a/tests/profiling_v2/test_main.py +++ b/tests/profiling/test_main.py @@ -5,8 +5,8 @@ import pytest -from tests.profiling_v2.collector import lock_utils -from tests.profiling_v2.collector import pprof_utils +from tests.profiling.collector import lock_utils +from tests.profiling.collector import pprof_utils from tests.utils import call_program diff --git a/tests/profiling_v2/test_profiler.py b/tests/profiling/test_profiler.py similarity index 100% rename from tests/profiling_v2/test_profiler.py rename to tests/profiling/test_profiler.py diff --git a/tests/profiling_v2/test_pytorch.py b/tests/profiling/test_pytorch.py similarity index 96% rename from tests/profiling_v2/test_pytorch.py rename to tests/profiling/test_pytorch.py index 673f5e25ac5..659b9067562 100644 --- a/tests/profiling_v2/test_pytorch.py +++ b/tests/profiling/test_pytorch.py @@ -3,7 +3,7 @@ import pytest -from tests.profiling_v2.collector import pprof_utils +from tests.profiling.collector import pprof_utils from tests.utils import call_program diff --git a/tests/profiling_v2/test_scheduler.py b/tests/profiling/test_scheduler.py similarity index 100% rename from tests/profiling_v2/test_scheduler.py rename to tests/profiling/test_scheduler.py diff --git a/tests/profiling_v2/test_uwsgi.py b/tests/profiling/test_uwsgi.py similarity index 99% rename from tests/profiling_v2/test_uwsgi.py rename to tests/profiling/test_uwsgi.py index 66d4910c44f..c9e7376bc59 100644 --- a/tests/profiling_v2/test_uwsgi.py +++ b/tests/profiling/test_uwsgi.py @@ -9,7 +9,7 @@ import pytest from tests.contrib.uwsgi import run_uwsgi -from tests.profiling_v2.collector import pprof_utils +from tests.profiling.collector import pprof_utils # uwsgi is not available on Windows diff --git a/tests/profiling_v2/uwsgi-app.py b/tests/profiling/uwsgi-app.py similarity index 100% rename from tests/profiling_v2/uwsgi-app.py rename to tests/profiling/uwsgi-app.py