From 72a50a294cce41c778f5b2ba539f75290d51c4e5 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sun, 18 Sep 2022 01:30:23 +0300 Subject: [PATCH 1/7] tests: Break up application_docker_images() More lines-of-code for the same task, but the image generators are much more clear now. This code has become a hassle to work with, and now it's simple again :) --- tests/conftest.py | 122 ++++++++++++------ ...kerfile.libpython => libpython.Dockerfile} | 0 tests/test_java.py | 14 +- tests/{test_libpython.py => test_python.py} | 12 +- tests/type_utils.py | 7 +- 5 files changed, 97 insertions(+), 58 deletions(-) rename tests/containers/python/{Dockerfile.libpython => libpython.Dockerfile} (100%) rename tests/{test_libpython.py => test_python.py} (73%) diff --git a/tests/conftest.py b/tests/conftest.py index 09dc28b28..3134b609f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,14 +2,13 @@ # Copyright (c) Granulate. All rights reserved. # Licensed under the AGPL3 License. See LICENSE.md in the project root for license information. # -import glob import os import stat import subprocess from contextlib import _GeneratorContextManager, contextmanager from functools import partial from pathlib import Path -from typing import Any, Callable, Generator, Iterable, Iterator, List, Mapping, Optional, cast +from typing import Any, Callable, Dict, Generator, Iterable, Iterator, List, Mapping, Optional, cast import docker import pytest @@ -184,47 +183,93 @@ def gprofiler_docker_image(docker_client: DockerClient) -> Iterable[Image]: yield docker_client.images.get("gprofiler") +def _build_image( + docker_client: DockerClient, runtime: str, dockerfile: str = "Dockerfile", **kwargs: Dict[str, Any] +) -> Image: + base_path = CONTAINERS_DIRECTORY / runtime + return docker_client.images.build(path=str(base_path), rm=True, dockerfile=str(base_path / dockerfile), **kwargs)[0] + + +def dotnet_images(docker_client: DockerClient) -> Mapping[str, Image]: + return { + "": _build_image(docker_client, "dotnet"), + } + + +def golang_images(docker_client: DockerClient) -> Mapping[str, Image]: + return { + "": _build_image(docker_client, "golang"), + } + + +def java_images(docker_client: DockerClient) -> Mapping[str, Image]: + return { + "": _build_image(docker_client, "java"), + "j9": _build_image(docker_client, "java", buildargs={"JAVA_BASE_IMAGE": "adoptopenjdk/openjdk8-openj9"}), + "zing": _build_image(docker_client, "java", dockerfile="zing.Dockerfile"), + "musl": _build_image(docker_client, "java", dockerfile="musl.Dockerfile"), + } + + +def native_images(docker_client: DockerClient) -> Mapping[str, Image]: + return { + "fp": _build_image(docker_client, "native", dockerfile="fp.Dockerfile"), + "dwarf": _build_image(docker_client, "native", dockerfile="dwarf.Dockerfile"), + "change_comm": _build_image(docker_client, "native", dockerfile="change_comm.Dockerfile"), + "thread_comm": _build_image(docker_client, "native", dockerfile="thread_comm.Dockerfile"), + } + + +def nodejs_images(docker_client: DockerClient) -> Mapping[str, Image]: + return { + "": _build_image(docker_client, "nodejs"), + } + + +def php_images(docker_client: DockerClient) -> Mapping[str, Image]: + return { + "": _build_image(docker_client, "php"), + } + + +def python_images(docker_client: DockerClient) -> Mapping[str, Image]: + return { + "": _build_image(docker_client, "python"), + "libpython": _build_image(docker_client, "python", dockerfile="libpython.Dockerfile"), + } + + +def ruby_images(docker_client: DockerClient) -> Mapping[str, Image]: + return { + "": _build_image(docker_client, "ruby"), + } + + +def image_name(runtime: str, image_tag: str) -> str: + return runtime + ("_" + image_tag if image_tag else "") + + @fixture(scope="session") def application_docker_images(docker_client: DockerClient) -> Mapping[str, Image]: images = {} - for runtime in os.listdir(str(CONTAINERS_DIRECTORY)): - if runtime == "native": - for dockerfile in glob.glob(str(CONTAINERS_DIRECTORY / runtime / "*.Dockerfile")): - suffix = os.path.splitext(os.path.basename(dockerfile))[0] - images[f"{runtime}_{suffix}"], _ = docker_client.images.build( - path=str(CONTAINERS_DIRECTORY / runtime), dockerfile=str(dockerfile), rm=True - ) - continue - - images[runtime], _ = docker_client.images.build(path=str(CONTAINERS_DIRECTORY / runtime), rm=True) - - # for java - add additional images - if runtime == "java": - images[runtime + "_j9"], _ = docker_client.images.build( - path=str(CONTAINERS_DIRECTORY / runtime), - rm=True, - buildargs={"JAVA_BASE_IMAGE": "adoptopenjdk/openjdk8-openj9"}, - ) - - images[runtime + "_zing"], _ = docker_client.images.build( - path=str(CONTAINERS_DIRECTORY / runtime), - rm=True, - dockerfile=str(CONTAINERS_DIRECTORY / runtime / "zing.Dockerfile"), - ) - - # build musl image if exists - musl_dockerfile = CONTAINERS_DIRECTORY / runtime / "musl.Dockerfile" - if musl_dockerfile.exists(): - images[runtime + "_musl"], _ = docker_client.images.build( - path=str(CONTAINERS_DIRECTORY / runtime), dockerfile=str(musl_dockerfile), rm=True - ) - + image_generators: Dict[str, Callable[[DockerClient], Mapping[str, Image]]] = { + "dotnet": dotnet_images, + "golang": golang_images, + "java": java_images, + "native": native_images, + "nodejs": nodejs_images, + "php": php_images, + "python": python_images, + "ruby": ruby_images, + } + for runtime, func in image_generators.items(): + images.update({image_name(runtime, tag): image for tag, image in func(docker_client).items()}) return images @fixture -def image_suffix() -> str: - # lets tests override this value and use a suffixed image, e.g _musl or _j9. +def application_image_tag() -> str: + # lets tests override this value and use a "tagged" image, e.g "musl" or "j9". return "" @@ -232,10 +277,9 @@ def image_suffix() -> str: def application_docker_image( application_docker_images: Mapping[str, Image], runtime: str, - image_suffix: str, + application_image_tag: str, ) -> Iterable[Image]: - runtime = runtime + image_suffix - yield application_docker_images[runtime] + yield application_docker_images[image_name(runtime, application_image_tag)] @fixture diff --git a/tests/containers/python/Dockerfile.libpython b/tests/containers/python/libpython.Dockerfile similarity index 100% rename from tests/containers/python/Dockerfile.libpython rename to tests/containers/python/libpython.Dockerfile diff --git a/tests/test_java.py b/tests/test_java.py index 1a98fd5c4..d10a4218c 100644 --- a/tests/test_java.py +++ b/tests/test_java.py @@ -48,6 +48,11 @@ ) +@pytest.fixture +def runtime() -> str: + return "java" + + def get_lib_path(application_pid: int, path: str) -> str: libs = set() for m in psutil.Process(application_pid).memory_maps(): @@ -78,11 +83,6 @@ def status_async_profiler(self) -> None: ) -@pytest.fixture -def runtime() -> str: - return "java" - - def test_async_profiler_already_running( application_pid: int, assert_collapsed: AssertInCollapsed, tmp_path: Path, caplog: LogCaptureFixture ) -> None: @@ -146,7 +146,7 @@ def test_java_async_profiler_cpu_mode( @pytest.mark.parametrize("in_container", [True]) -@pytest.mark.parametrize("image_suffix", ["_musl"]) +@pytest.mark.parametrize("application_image_tag", ["musl"]) def test_java_async_profiler_musl_and_cpu( tmp_path: Path, application_pid: int, @@ -339,7 +339,7 @@ def test_async_profiler_stops_after_given_timeout( @pytest.mark.parametrize("in_container", [True]) -@pytest.mark.parametrize("image_suffix,search_for", [("_j9", "OpenJ9"), ("_zing", "Zing")]) +@pytest.mark.parametrize("application_image_tag,search_for", [("j9", "OpenJ9"), ("zing", "Zing")]) def test_sanity_other_jvms( tmp_path: Path, application_pid: int, diff --git a/tests/test_libpython.py b/tests/test_python.py similarity index 73% rename from tests/test_libpython.py rename to tests/test_python.py index 305934c94..88e3d24c3 100644 --- a/tests/test_libpython.py +++ b/tests/test_python.py @@ -6,12 +6,9 @@ from threading import Event import pytest -from docker import DockerClient from docker.models.containers import Container -from docker.models.images import Image from gprofiler.profilers.python import PythonProfiler -from tests import CONTAINERS_DIRECTORY from tests.conftest import AssertInCollapsed from tests.utils import snapshot_pid_collapsed @@ -21,15 +18,8 @@ def runtime() -> str: return "python" -@pytest.fixture(scope="session") -def application_docker_image(docker_client: DockerClient) -> Image: - dockerfile = CONTAINERS_DIRECTORY / "python" / "Dockerfile.libpython" - image: Image = docker_client.images.build(path=str(dockerfile.parent), dockerfile=str(dockerfile), rm=True)[0] - yield image - docker_client.images.remove(image.id, force=True) - - @pytest.mark.parametrize("in_container", [True]) +@pytest.mark.parametrize("application_image_tag", ["libpython"]) def test_python_select_by_libpython( tmp_path: Path, application_docker_container: Container, diff --git a/tests/type_utils.py b/tests/type_utils.py index 9f0a42c6e..369b999f8 100644 --- a/tests/type_utils.py +++ b/tests/type_utils.py @@ -2,7 +2,7 @@ # Copyright (c) Granulate. All rights reserved. # Licensed under the AGPL3 License. See LICENSE.md in the project root for license information. # -from typing import Optional, TypeVar +from typing import Any, Optional, Type, TypeVar T = TypeVar("T") @@ -10,3 +10,8 @@ def cast_away_optional(arg: Optional[T]) -> T: assert arg is not None return arg + + +def assert_cast(typ: Type[T], arg: Any) -> T: + assert isinstance(arg, typ) + return arg From 68a9941f51f7f089e503229498d43f6139f3e92b Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sun, 18 Sep 2022 01:43:14 +0300 Subject: [PATCH 2/7] simplify --- tests/conftest.py | 106 +++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 68 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3134b609f..58195ea4e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -184,86 +184,56 @@ def gprofiler_docker_image(docker_client: DockerClient) -> Iterable[Image]: def _build_image( - docker_client: DockerClient, runtime: str, dockerfile: str = "Dockerfile", **kwargs: Dict[str, Any] + docker_client: DockerClient, runtime: str, dockerfile: str = "Dockerfile", **kwargs: Mapping[str, Any] ) -> Image: base_path = CONTAINERS_DIRECTORY / runtime return docker_client.images.build(path=str(base_path), rm=True, dockerfile=str(base_path / dockerfile), **kwargs)[0] -def dotnet_images(docker_client: DockerClient) -> Mapping[str, Image]: - return { - "": _build_image(docker_client, "dotnet"), - } - - -def golang_images(docker_client: DockerClient) -> Mapping[str, Image]: - return { - "": _build_image(docker_client, "golang"), - } - - -def java_images(docker_client: DockerClient) -> Mapping[str, Image]: - return { - "": _build_image(docker_client, "java"), - "j9": _build_image(docker_client, "java", buildargs={"JAVA_BASE_IMAGE": "adoptopenjdk/openjdk8-openj9"}), - "zing": _build_image(docker_client, "java", dockerfile="zing.Dockerfile"), - "musl": _build_image(docker_client, "java", dockerfile="musl.Dockerfile"), - } - - -def native_images(docker_client: DockerClient) -> Mapping[str, Image]: - return { - "fp": _build_image(docker_client, "native", dockerfile="fp.Dockerfile"), - "dwarf": _build_image(docker_client, "native", dockerfile="dwarf.Dockerfile"), - "change_comm": _build_image(docker_client, "native", dockerfile="change_comm.Dockerfile"), - "thread_comm": _build_image(docker_client, "native", dockerfile="thread_comm.Dockerfile"), - } - - -def nodejs_images(docker_client: DockerClient) -> Mapping[str, Image]: - return { - "": _build_image(docker_client, "nodejs"), - } - - -def php_images(docker_client: DockerClient) -> Mapping[str, Image]: - return { - "": _build_image(docker_client, "php"), - } - - -def python_images(docker_client: DockerClient) -> Mapping[str, Image]: - return { - "": _build_image(docker_client, "python"), - "libpython": _build_image(docker_client, "python", dockerfile="libpython.Dockerfile"), - } - - -def ruby_images(docker_client: DockerClient) -> Mapping[str, Image]: - return { - "": _build_image(docker_client, "ruby"), - } - - def image_name(runtime: str, image_tag: str) -> str: return runtime + ("_" + image_tag if image_tag else "") @fixture(scope="session") def application_docker_images(docker_client: DockerClient) -> Mapping[str, Image]: - images = {} - image_generators: Dict[str, Callable[[DockerClient], Mapping[str, Image]]] = { - "dotnet": dotnet_images, - "golang": golang_images, - "java": java_images, - "native": native_images, - "nodejs": nodejs_images, - "php": php_images, - "python": python_images, - "ruby": ruby_images, + runtime_image_listing: Dict[str, Dict[str, Dict[str, Any]]] = { + "dotnet": { + "": {}, + }, + "golang": { + "": {}, + }, + "java": { + "": {}, + "j9": dict(buildargs={"JAVA_BASE_IMAGE": "adoptopenjdk/openjdk8-openj9"}), + "zing": dict(dockerfile="zing.Dockerfile"), + "musl": dict(dockerfile="musl.Dockerfile"), + }, + "native": { + "fp": dict(dockerfile="fp.Dockerfile"), + "dwarf": dict(dockerfile="dwarf.Dockerfile"), + "change_comm": dict(dockerfile="change_comm.Dockerfile"), + "thread_comm": dict(dockerfile="thread_comm.Dockerfile"), + }, + "nodejs": { + "": {}, + }, + "php": { + "": {}, + }, + "python": { + "": {}, + "libpython": dict(dockerfile="libpython.Dockerfile"), + }, + "ruby": {"": {}}, } - for runtime, func in image_generators.items(): - images.update({image_name(runtime, tag): image for tag, image in func(docker_client).items()}) + + images = {} + for runtime, tags_listing in runtime_image_listing.items(): + for tag, args in tags_listing.items(): + name = image_name(runtime, tag) + assert name not in images + images[name] = _build_image(docker_client, runtime, **args) return images From f156a13bb7584176dd305aa685098f1f1062ee1c Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sun, 18 Sep 2022 02:06:15 +0300 Subject: [PATCH 3/7] tests: python: Add matrix tests Full matrix of feature tests for py-spy & PyPerf across all desired versions. This is what I suggested in #287 and finally got around to implement. I rather have these in place before performing any other changes on PyPerf/py-spy. --- tests/conftest.py | 14 +++++++ tests/containers/python/lister.py | 15 ++++--- tests/test_python.py | 65 +++++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 58195ea4e..9f4125327 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -224,6 +224,20 @@ def application_docker_images(docker_client: DockerClient) -> Mapping[str, Image "python": { "": {}, "libpython": dict(dockerfile="libpython.Dockerfile"), + "2.7-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "2.7-slim"}), + "2.7-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "2.7-alpine"}), + "3.5-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.5-slim"}), + "3.5-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.5-alpine"}), + "3.6-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.6-slim"}), + "3.6-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.6-alpine"}), + "3.7-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.7-slim"}), + "3.7-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.7-alpine"}), + "3.8-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.8-slim"}), + "3.8-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.8-alpine"}), + "3.9-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.9-slim"}), + "3.9-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.9-alpine"}), + "3.10-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.10-slim"}), + "3.10-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.10-alpine"}), }, "ruby": {"": {}}, } diff --git a/tests/containers/python/lister.py b/tests/containers/python/lister.py index b81c45eb0..0a0f2c7a4 100755 --- a/tests/containers/python/lister.py +++ b/tests/containers/python/lister.py @@ -6,22 +6,27 @@ import os from threading import Thread -import yaml - class Lister(object): @classmethod - def lister(cls) -> None: + def lister(cls): + # type: () -> None os.listdir("/") # have some kernel stacks & Python stacks from a class method class Burner(object): - def burner(self) -> None: + def burner(self): + # type: () -> None while True: # have some Python stacks from an instance method pass -def parser() -> None: +def parser(): + # type: () -> None + try: + import yaml + except ImportError: + return # not required in this test while True: # Have some package stacks. # Notice the name of the package name (PyYAML) is different from the name of the module (yaml) diff --git a/tests/test_python.py b/tests/test_python.py index 88e3d24c3..9f31421d7 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -5,12 +5,13 @@ from pathlib import Path from threading import Event +import psutil import pytest -from docker.models.containers import Container +from granulate_utils.linux.process import is_musl from gprofiler.profilers.python import PythonProfiler from tests.conftest import AssertInCollapsed -from tests.utils import snapshot_pid_collapsed +from tests.utils import assert_function_in_collapsed, snapshot_pid_collapsed @pytest.fixture @@ -22,7 +23,7 @@ def runtime() -> str: @pytest.mark.parametrize("application_image_tag", ["libpython"]) def test_python_select_by_libpython( tmp_path: Path, - application_docker_container: Container, + application_pid: int, assert_collapsed: AssertInCollapsed, ) -> None: """ @@ -32,6 +33,62 @@ def test_python_select_by_libpython( This test runs a Python named "shmython". """ with PythonProfiler(1000, 1, Event(), str(tmp_path), False, "pyspy", True, None) as profiler: - process_collapsed = snapshot_pid_collapsed(profiler, application_docker_container.attrs["State"]["Pid"]) + process_collapsed = snapshot_pid_collapsed(profiler, application_pid) assert_collapsed(process_collapsed) assert all(stack.startswith("shmython") for stack in process_collapsed.keys()) + + +@pytest.mark.parametrize("in_container", [True]) +@pytest.mark.parametrize( + "application_image_tag", + [ + "2.7-glibc", + "2.7-musl", + "3.5-glibc", + "3.5-musl", + "3.6-glibc", + "3.6-musl", + "3.7-glibc", + "3.7-musl", + "3.8-glibc", + "3.8-musl", + "3.9-glibc", + "3.9-musl", + "3.10-glibc", + "3.10-musl", + ], +) +@pytest.mark.parametrize("profiler_type", ["py-spy", "pyperf"]) +def test_python_matrix( + tmp_path: Path, + application_pid: int, + assert_collapsed: AssertInCollapsed, + profiler_type: str, + application_image_tag: str, +) -> None: + python_version, libc = application_image_tag.split("-") + + if python_version == "3.5" and profiler_type == "pyperf": + pytest.skip("PyPerf doesn't support Python 3.5!") + + with PythonProfiler(1000, 2, Event(), str(tmp_path), False, profiler_type, True, None) as profiler: + process_collapsed = snapshot_pid_collapsed(profiler, application_pid) + + assert_collapsed(process_collapsed) + # searching for "python_version.", because ours is without the patchlevel. + assert_function_in_collapsed(f"standard-library=={python_version}.", process_collapsed) + + assert libc in ("musl", "glibc") + assert (libc == "musl") == is_musl(psutil.Process(application_pid)) + + if profiler_type == "pyperf": + # we expect to see kernel code + assert_function_in_collapsed("do_syscall_64_[k]", process_collapsed) + # and native user code + assert_function_in_collapsed( + "PyEval_EvalFrameEx_[pn]" if python_version == "2.7" else "_PyEval_EvalFrameDefault_[pn]", process_collapsed + ) + # ensure class name exists for instance methods + assert_function_in_collapsed("lister.Burner.burner", process_collapsed) + # ensure class name exists for class methods + assert_function_in_collapsed("lister.Lister.lister", process_collapsed) From a902012049d77ad9f380a6f92c486c558bd908d7 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Mon, 19 Sep 2022 02:32:31 +0300 Subject: [PATCH 4/7] Add forgotten file --- tests/containers/python/matrix.Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/containers/python/matrix.Dockerfile diff --git a/tests/containers/python/matrix.Dockerfile b/tests/containers/python/matrix.Dockerfile new file mode 100644 index 000000000..e38fc2e36 --- /dev/null +++ b/tests/containers/python/matrix.Dockerfile @@ -0,0 +1,6 @@ +ARG PYTHON_IMAGE_TAG +FROM python:${PYTHON_IMAGE_TAG} + +WORKDIR /app +ADD lister.py /app +CMD ["python", "lister.py"] From 8f966247b5d1601277f68a6348a80fbda7172702 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Mon, 19 Sep 2022 02:41:18 +0300 Subject: [PATCH 5/7] Add uwsgi tests --- tests/conftest.py | 37 ++++++++------ tests/containers/python/uwsgi.Dockerfile | 9 ++++ tests/test_python.py | 62 +++++++++++++++--------- 3 files changed, 71 insertions(+), 37 deletions(-) create mode 100644 tests/containers/python/uwsgi.Dockerfile diff --git a/tests/conftest.py b/tests/conftest.py index 9f4125327..801b3d937 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -81,6 +81,7 @@ def java_command_line(tmp_path: Path, java_args: List[str]) -> List[str]: @fixture def dotnet_command_line(tmp_path: Path) -> List[str]: + return [] class_path = tmp_path / "dotnet" / "Fibonacci" class_path.mkdir(parents=True) chmod_path_parts(class_path, stat.S_IRGRP | stat.S_IROTH | stat.S_IXGRP | stat.S_IXOTH) @@ -224,20 +225,28 @@ def application_docker_images(docker_client: DockerClient) -> Mapping[str, Image "python": { "": {}, "libpython": dict(dockerfile="libpython.Dockerfile"), - "2.7-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "2.7-slim"}), - "2.7-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "2.7-alpine"}), - "3.5-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.5-slim"}), - "3.5-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.5-alpine"}), - "3.6-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.6-slim"}), - "3.6-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.6-alpine"}), - "3.7-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.7-slim"}), - "3.7-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.7-alpine"}), - "3.8-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.8-slim"}), - "3.8-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.8-alpine"}), - "3.9-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.9-slim"}), - "3.9-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.9-alpine"}), - "3.10-glibc": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.10-slim"}), - "3.10-musl": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.10-alpine"}), + "2.7-glibc-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "2.7-slim"}), + "2.7-musl-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "2.7-alpine"}), + "3.5-glibc-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.5-slim"}), + "3.5-musl-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.5-alpine"}), + "3.6-glibc-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.6-slim"}), + "3.6-musl-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.6-alpine"}), + "3.7-glibc-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.7-slim"}), + "3.7-musl-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.7-alpine"}), + "3.8-glibc-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.8-slim"}), + "3.8-musl-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.8-alpine"}), + "3.9-glibc-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.9-slim"}), + "3.9-musl-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.9-alpine"}), + "3.10-glibc-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.10-slim"}), + "3.10-musl-python": dict(dockerfile="matrix.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.10-alpine"}), + "2.7-glibc-uwsgi": dict( + dockerfile="uwsgi.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "2.7"} + ), # not slim - need gcc + # "2.7-musl-uwsgi": dict(dockerfile="uwsgi.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "2.7-alpine"}), + "3.7-glibc-uwsgi": dict( + dockerfile="uwsgi.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.7"} + ), # not slim - need gcc + # "3.7-musl-uwsgi": dict(dockerfile="uwsgi.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.7-alpine"}), }, "ruby": {"": {}}, } diff --git a/tests/containers/python/uwsgi.Dockerfile b/tests/containers/python/uwsgi.Dockerfile new file mode 100644 index 000000000..99bfd49af --- /dev/null +++ b/tests/containers/python/uwsgi.Dockerfile @@ -0,0 +1,9 @@ +ARG PYTHON_IMAGE_TAG +FROM python:${PYTHON_IMAGE_TAG} + +WORKDIR /app + +RUN pip install uwsgi + +ADD lister.py /app +CMD ["uwsgi", "--py", "lister.py"] diff --git a/tests/test_python.py b/tests/test_python.py index 9f31421d7..503a3a378 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2,6 +2,7 @@ # Copyright (c) Granulate. All rights reserved. # Licensed under the AGPL3 License. See LICENSE.md in the project root for license information. # +import os from pathlib import Path from threading import Event @@ -11,7 +12,7 @@ from gprofiler.profilers.python import PythonProfiler from tests.conftest import AssertInCollapsed -from tests.utils import assert_function_in_collapsed, snapshot_pid_collapsed +from tests.utils import assert_function_in_collapsed, snapshot_pid_collapsed, snapshot_pid_profile @pytest.fixture @@ -42,20 +43,24 @@ def test_python_select_by_libpython( @pytest.mark.parametrize( "application_image_tag", [ - "2.7-glibc", - "2.7-musl", - "3.5-glibc", - "3.5-musl", - "3.6-glibc", - "3.6-musl", - "3.7-glibc", - "3.7-musl", - "3.8-glibc", - "3.8-musl", - "3.9-glibc", - "3.9-musl", - "3.10-glibc", - "3.10-musl", + "2.7-glibc-python", + "2.7-musl-python", + "3.5-glibc-python", + "3.5-musl-python", + "3.6-glibc-python", + "3.6-musl-python", + "3.7-glibc-python", + "3.7-musl-python", + "3.8-glibc-python", + "3.8-musl-python", + "3.9-glibc-python", + "3.9-musl-python", + "3.10-glibc-python", + "3.10-musl-python", + "2.7-glibc-uwsgi", + # "2.7-musl-uwsgi", + "3.7-glibc-uwsgi", + # "3.7-musl-uwsgi", ], ) @pytest.mark.parametrize("profiler_type", ["py-spy", "pyperf"]) @@ -66,29 +71,40 @@ def test_python_matrix( profiler_type: str, application_image_tag: str, ) -> None: - python_version, libc = application_image_tag.split("-") + python_version, libc, app = application_image_tag.split("-") if python_version == "3.5" and profiler_type == "pyperf": pytest.skip("PyPerf doesn't support Python 3.5!") with PythonProfiler(1000, 2, Event(), str(tmp_path), False, profiler_type, True, None) as profiler: - process_collapsed = snapshot_pid_collapsed(profiler, application_pid) + profile = snapshot_pid_profile(profiler, application_pid) - assert_collapsed(process_collapsed) + collapsed = profile.stacks + + assert_collapsed(collapsed) # searching for "python_version.", because ours is without the patchlevel. - assert_function_in_collapsed(f"standard-library=={python_version}.", process_collapsed) + assert_function_in_collapsed(f"standard-library=={python_version}.", collapsed) assert libc in ("musl", "glibc") assert (libc == "musl") == is_musl(psutil.Process(application_pid)) if profiler_type == "pyperf": # we expect to see kernel code - assert_function_in_collapsed("do_syscall_64_[k]", process_collapsed) + assert_function_in_collapsed("do_syscall_64_[k]", collapsed) # and native user code assert_function_in_collapsed( - "PyEval_EvalFrameEx_[pn]" if python_version == "2.7" else "_PyEval_EvalFrameDefault_[pn]", process_collapsed + "PyEval_EvalFrameEx_[pn]" if python_version == "2.7" else "_PyEval_EvalFrameDefault_[pn]", collapsed ) # ensure class name exists for instance methods - assert_function_in_collapsed("lister.Burner.burner", process_collapsed) + assert_function_in_collapsed("lister.Burner.burner", collapsed) # ensure class name exists for class methods - assert_function_in_collapsed("lister.Lister.lister", process_collapsed) + assert_function_in_collapsed("lister.Lister.lister", collapsed) + + assert profile.app_metadata is not None + assert os.path.basename(profile.app_metadata["execfn"]) == app + # searching for "python_version.", because ours is without the patchlevel. + assert profile.app_metadata["python_version"].startswith(f"Python {python_version}.") + if python_version == "2.7" and app == "python": + assert profile.app_metadata["sys_maxunicode"] == "1114111" + else: + assert profile.app_metadata["sys_maxunicode"] is None From 5dedf4f8346c2c281d2b305885ca69c050aa706b Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Thu, 22 Sep 2022 00:54:51 +0300 Subject: [PATCH 6/7] fixes --- tests/conftest.py | 1 - tests/test_python.py | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 801b3d937..ebea98fee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -81,7 +81,6 @@ def java_command_line(tmp_path: Path, java_args: List[str]) -> List[str]: @fixture def dotnet_command_line(tmp_path: Path) -> List[str]: - return [] class_path = tmp_path / "dotnet" / "Fibonacci" class_path.mkdir(parents=True) chmod_path_parts(class_path, stat.S_IRGRP | stat.S_IROTH | stat.S_IXGRP | stat.S_IXOTH) diff --git a/tests/test_python.py b/tests/test_python.py index 503a3a378..137acc9c3 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -76,6 +76,9 @@ def test_python_matrix( if python_version == "3.5" and profiler_type == "pyperf": pytest.skip("PyPerf doesn't support Python 3.5!") + if python_version == "2.7" and profiler_type == "pyperf" and libc == "glibc" and app == "uwsgi": + pytest.xfail("This combination fails, see https://github.com/Granulate/gprofiler/issues/485") + with PythonProfiler(1000, 2, Event(), str(tmp_path), False, profiler_type, True, None) as profiler: profile = snapshot_pid_profile(profiler, application_pid) From d15bbe1c36f45a53169090c7443bfb2ee42e1845 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Thu, 22 Sep 2022 01:17:28 +0300 Subject: [PATCH 7/7] fix musl images --- tests/conftest.py | 4 ++-- tests/containers/python/uwsgi.Dockerfile | 3 +++ tests/test_python.py | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ebea98fee..86858c678 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -241,11 +241,11 @@ def application_docker_images(docker_client: DockerClient) -> Mapping[str, Image "2.7-glibc-uwsgi": dict( dockerfile="uwsgi.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "2.7"} ), # not slim - need gcc - # "2.7-musl-uwsgi": dict(dockerfile="uwsgi.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "2.7-alpine"}), + "2.7-musl-uwsgi": dict(dockerfile="uwsgi.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "2.7-alpine"}), "3.7-glibc-uwsgi": dict( dockerfile="uwsgi.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.7"} ), # not slim - need gcc - # "3.7-musl-uwsgi": dict(dockerfile="uwsgi.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.7-alpine"}), + "3.7-musl-uwsgi": dict(dockerfile="uwsgi.Dockerfile", buildargs={"PYTHON_IMAGE_TAG": "3.7-alpine"}), }, "ruby": {"": {}}, } diff --git a/tests/containers/python/uwsgi.Dockerfile b/tests/containers/python/uwsgi.Dockerfile index 99bfd49af..9520db920 100644 --- a/tests/containers/python/uwsgi.Dockerfile +++ b/tests/containers/python/uwsgi.Dockerfile @@ -3,6 +3,9 @@ FROM python:${PYTHON_IMAGE_TAG} WORKDIR /app +# to build uwsgi +RUN if grep -q Alpine /etc/os-release; then apk add gcc libc-dev linux-headers; fi + RUN pip install uwsgi ADD lister.py /app diff --git a/tests/test_python.py b/tests/test_python.py index 137acc9c3..e0d92f3eb 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -58,9 +58,9 @@ def test_python_select_by_libpython( "3.10-glibc-python", "3.10-musl-python", "2.7-glibc-uwsgi", - # "2.7-musl-uwsgi", + "2.7-musl-uwsgi", "3.7-glibc-uwsgi", - # "3.7-musl-uwsgi", + "3.7-musl-uwsgi", ], ) @pytest.mark.parametrize("profiler_type", ["py-spy", "pyperf"]) @@ -76,7 +76,7 @@ def test_python_matrix( if python_version == "3.5" and profiler_type == "pyperf": pytest.skip("PyPerf doesn't support Python 3.5!") - if python_version == "2.7" and profiler_type == "pyperf" and libc == "glibc" and app == "uwsgi": + if python_version == "2.7" and profiler_type == "pyperf" and app == "uwsgi": pytest.xfail("This combination fails, see https://github.com/Granulate/gprofiler/issues/485") with PythonProfiler(1000, 2, Event(), str(tmp_path), False, profiler_type, True, None) as profiler: