Skip to content

Feature/update python support #963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
51e74b5
Upgrade python version support (3.10+)
sobolron Mar 30, 2025
1cd8234
Fix python 3.9 to 3.10 in few places
sobolron Mar 30, 2025
9cb9c75
Fix ContextManager typing for 3.10 AND 3.13.
sobolron Apr 7, 2025
1b92d4d
Try upgrading to newest exe requirements
sobolron Apr 7, 2025
ca32930
Try to patch around the use of staticx to maybe find something that w…
sobolron Apr 7, 2025
06cd099
Removed staticx_patch as the code for it was changed in https://githu…
sobolron Apr 7, 2025
95057e1
Fix staticx issues, with scons and with PyInstaller issues
sobolron Apr 8, 2025
07c5b43
Try to fix RUNPATH issues... Not sure this will be OK.
sobolron Apr 8, 2025
3a0dcfc
Add patchelf installation to commands for dotnet-trace installations
sobolron Apr 8, 2025
7393a14
Fix dotnet-trace elf path for build
sobolron Apr 8, 2025
d0e5e54
Remove bad pip install line
sobolron Apr 8, 2025
9655a6f
Fix things related to staticx usage in pyperf build
sobolron Apr 8, 2025
73875e1
Fix issue for linter
sobolron Apr 8, 2025
1c05cfd
Fix issue for Docker linter
sobolron Apr 8, 2025
065811c
Fix typo in staticx run
sobolron Apr 8, 2025
84b4c5b
Move some code to maintain AARCH64 support (without pyperf)
sobolron Apr 8, 2025
6f4fe03
Fix staticx installation in x86_64 to include the correct code, and m…
sobolron Apr 8, 2025
eb3de63
Added new setuptools after issues with build after rebase
sobolron Apr 8, 2025
3709968
Try fixing things using better exe-requirements and better build deps…
sobolron Apr 14, 2025
fec2f31
Update staticx revision with fixes.
sobolron Apr 14, 2025
40805de
Fix bad addition of --always.
sobolron Apr 14, 2025
592204b
Fix issue with isolated env doesn't have scons
sobolron Apr 14, 2025
7226b3b
Add fix for gprofiler halts when creating new processes in another pg…
slicklash Oct 8, 2024
4e50ede
Add missing python versions to runtime image listing for tests
sobolron Apr 21, 2025
ce54d5f
Update bpf_get_stack_offset code to fix for kernel 6.9+ with TOP_OF_K…
sobolron Apr 21, 2025
adeaa6f
Add retries for curl for various errors
sobolron Apr 21, 2025
8701b98
Update bpf_get_stack_offset with fix
sobolron Apr 21, 2025
642597b
Fix bcc pyperf to support python 3.13
sobolron Apr 22, 2025
7884be4
Allow metadata to find python version for 3.13
sobolron Apr 22, 2025
bba41a7
Update bcc version, hopefully with offsets fixed
sobolron Apr 22, 2025
9a978aa
Update pyperf to where I fixed offsets again after looking it up dyna…
sobolron Apr 23, 2025
e23b4d9
Update bcc after fixing offsets and code logic for python 3.12 and 3.13
sobolron Apr 24, 2025
bbf0521
Update bcc with small fixes for verifier
sobolron Apr 24, 2025
8694036
Update bcc with fixes for python <3.11
sobolron Apr 24, 2025
be3957a
Put pyperf verbosity on in tests
sobolron Apr 24, 2025
cb0c827
Revert change made to use var_count as it seems it doesn't work for c…
sobolron Apr 27, 2025
6d8ac52
Attempt to add bp to allow better symbol resolution for native frames.
sobolron Apr 27, 2025
e488e38
Downgrade ubuntu for tests to see if it helps
sobolron Apr 27, 2025
c744700
Increase timeouts
IlayRosenberg Apr 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/build-on-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ jobs:
uses: actions/checkout@v3
with:
submodules: true


- uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: run the build script
run: .\scripts\windows\build.bat

Expand Down
13 changes: 7 additions & 6 deletions .github/workflows/build-test-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ jobs:
- debian:10
- debian:11
steps:
- name: Set up Python 3.8
- name: Set up Python 3.10
uses: actions/setup-python@v2
with:
python-version: "3.8"
python-version: "3.10"

- name: Install Java
uses: actions/setup-java@v1
Expand Down Expand Up @@ -213,15 +213,16 @@ jobs:
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
needs: build-container-x64

runs-on: ubuntu-latest
runs-on: ubuntu-22.04

strategy:
fail-fast: false # helps detecting flakiness / errors specific to one Python version
matrix:
python-version:
- "3.8"
- "3.9"
- "3.10" # see https://github.com/Granulate/gprofiler/issues/502, we need 3.10.0, others fail PyPerf tests.
- "3.10"
- "3.11"
- "3.12"
- "3.13"

steps:
- name: Set up Python ${{ matrix.python-version }}
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/linters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ jobs:
strategy:
matrix:
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"

runs-on: ubuntu-latest

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ The playbook defines 2 more variables:
* `gprofiler_args` - additional arguments to pass to gProfiler, empty by default. You can use it to pass, for example, `'--profiling-frequency 15'` to change the frequency.

## Running from source
gProfiler requires Python 3.8+ to run.
gProfiler requires Python 3.10+ to run.

```bash
pip3 install -r requirements.txt
Expand Down
2 changes: 1 addition & 1 deletion deploy/docker-compose/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: "3.9"
version: "3.10"

services:
gprofiler:
Expand Down
30 changes: 14 additions & 16 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
pytest==7.0.1
flake8==6.0.0
black==23.3.0
mypy==0.971
isort==5.12.0
PyYAML==6.0
docker==7.0.0
types-requests==0.1.9
types-dataclasses==0.6.6
types-psutil==5.8.19
types-PyYAML==6.0.3
types-pkg-resources==0.1.3
types-protobuf==3.19.22
types-toml==0.10.8
types-retry==0.9.9
types-beautifulsoup4==4.11.1
pytest==8.3.5
flake8==7.2.0
black==25.1.0
mypy==1.15.0
isort==6.0.1
PyYAML==6.0.2
docker==7.1.0
types-requests~=2.32.0
types-psutil~=7.0.0
types-PyYAML~=6.0.12
types-protobuf~=5.29.1
types-toml~=0.10.8
types-retry~=0.9.9
types-beautifulsoup4~=4.12.0
15 changes: 2 additions & 13 deletions exe-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,2 @@
# requirements for the standalone executable
pyinstaller==4.6; platform.machine == "x86_64"
# aarch64 requires a later version due to the use of a newer centos version.
# see https://github.com/pyinstaller/pyinstaller/issues/5540
pyinstaller==4.10; platform.machine == "aarch64" or sys.platform == "win32"
# for aarch64 we build a slightly patched version
# I tried upgrading to 0.13.6 but it crashes when the botoloader is run as non-root :/
# I got this error in gdb:
# (gdb) run
# Starting program: /path/to/build/x86_64/gprofiler
# During startup program terminated with signal SIGSEGV, Segmentation fault.
# staying with 0.12.1 for the mean time...
staticx==0.12.1; platform.machine == "x86_64"
pyinstaller==6.12.0
staticx @ git+https://github.com/Granulate/staticx.git@33eefdadc72832d5aa67c0792768c9e76afb746d; platform.machine == "x86_64"
25 changes: 11 additions & 14 deletions executable.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ RUN mv "/tmp/rbspy/target/$(uname -m)-unknown-linux-musl/release/rbspy" /tmp/rbs
FROM mcr.microsoft.com/dotnet/sdk${DOTNET_BUILDER} as dotnet-builder
WORKDIR /tmp
RUN apt-get update && \
dotnet tool install --global dotnet-trace --version 6.0.351802
dotnet tool install --global dotnet-trace --version 6.0.351802 && \
apt-get install -y --no-install-recommends patchelf

RUN cp -r "$HOME/.dotnet" "/tmp/dotnet"
COPY scripts/dotnet_prepare_dependencies.sh .
Expand Down Expand Up @@ -77,11 +78,12 @@ RUN ./phpspy_build.sh
# async-profiler glibc
FROM centos${AP_BUILDER_CENTOS} AS async-profiler-builder-glibc
WORKDIR /tmp
COPY scripts/async_profiler_env_glibc.sh scripts/fix_centos7.sh ./
COPY scripts/async_profiler_env_glibc.sh scripts/fix_centos7.sh scripts/pdeathsigger.c ./
RUN if grep -q "CentOS Linux 7" /etc/os-release ; then \
./fix_centos7.sh; \
fi
RUN ./async_profiler_env_glibc.sh
RUN ./async_profiler_env_glibc.sh && \
gcc -static -o pdeathsigger pdeathsigger.c

COPY scripts/async_profiler_build_shared.sh .
RUN ./async_profiler_build_shared.sh
Expand Down Expand Up @@ -124,7 +126,6 @@ COPY --from=perf-builder /bpftool /bpftool

WORKDIR /bcc
COPY scripts/staticx_for_pyperf_patch.diff .
COPY scripts/staticx_patch.diff .
COPY scripts/bcc_helpers_build.sh .
COPY scripts/pyperf_env.sh .
RUN ./pyperf_env.sh --with-staticx
Expand Down Expand Up @@ -177,6 +178,7 @@ WORKDIR /app
RUN yum --setopt=skip_missing_names_on_install=False install -y \
gcc \
curl \
glibc-static \
libicu && \
yum clean all

Expand All @@ -191,10 +193,6 @@ RUN set -e; \
if [ "$(uname -m)" = "aarch64" ]; then \
ln -s /usr/lib64/python3.10/lib-dynload /usr/lib/python3.10/lib-dynload; \
fi
RUN set -e; \
if [ "$(uname -m)" = "aarch64" ]; then \
python3 -m pip install --no-cache-dir 'wheel==0.37.0' 'scons==4.2.0'; \
fi

# we want the latest pip
# hadolint ignore=DL3013
Expand Down Expand Up @@ -255,6 +253,7 @@ COPY --from=async-profiler-builder-glibc /usr/bin/xargs gprofiler/resources/php/

COPY --from=async-profiler-builder-glibc /tmp/async-profiler/build/bin/asprof gprofiler/resources/java/asprof
COPY --from=async-profiler-builder-glibc /tmp/async-profiler/build/async-profiler-version gprofiler/resources/java/async-profiler-version
COPY --from=async-profiler-builder-glibc /tmp/pdeathsigger gprofiler/resources/pdeathsigger
COPY --from=async-profiler-centos-min-test-glibc /libasyncProfiler.so gprofiler/resources/java/glibc/libasyncProfiler.so
COPY --from=async-profiler-builder-musl /tmp/async-profiler/build/lib/libasyncProfiler.so gprofiler/resources/java/musl/libasyncProfiler.so
COPY --from=node-package-builder-musl /tmp/module_build gprofiler/resources/node/module/musl
Expand All @@ -275,15 +274,13 @@ RUN pyinstaller pyinstaller.spec \
&& test -f build/pyinstaller/warn-pyinstaller.txt \
&& ./check_pyinstaller.sh

# for aarch64 - build a patched version of staticx 0.13.6. we remove calls to getpwnam and getgrnam, for these end up doing dlopen()s which
# crash the staticx bootloader. we don't need them anyway (all files in our staticx tar are uid 0 and we don't need the names translation)
COPY scripts/staticx_patch.diff staticx_patch.diff
# We need staticx main as version wasn't released yet for PyInstaller hooks throwing on non elfs.
# Removed build isolation as from some reason there's an issue with scons on the isolated env.
# hadolint ignore=DL3003
RUN if [ "$(uname -m)" = "aarch64" ]; then \
git clone -b v0.13.6 https://github.com/JonathonReinhart/staticx.git && \
git clone https://github.com/Granulate/staticx.git && \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should move this repo to intel organization? @sobolron

cd staticx && \
git reset --hard 819d8eafecbaab3646f70dfb1e3e19f6bbc017f8 && \
git apply ../staticx_patch.diff && \
git checkout 33eefdadc72832d5aa67c0792768c9e76afb746d && \
ln -s libnss_files.so.2 /lib64/libnss_files.so && \
ln -s libnss_dns.so.2 /lib64/libnss_dns.so && \
python3 -m pip install --no-cache-dir . ; \
Expand Down
38 changes: 4 additions & 34 deletions gprofiler/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@
import logging.handlers
import os
import shutil
import signal
import sys
import time
import traceback
from pathlib import Path
from threading import Event
from types import FrameType, TracebackType
from types import TracebackType
from typing import Iterable, List, Optional, Type, cast

import configargparse
Expand Down Expand Up @@ -73,6 +72,7 @@
reset_umask,
resource_path,
run_process,
setup_signals,
)
from gprofiler.utils.fs import escape_filename, mkdir_owned_root_wrapper
from gprofiler.utils.proxy import get_https_proxy
Expand All @@ -97,21 +97,6 @@

UPLOAD_FILE_SUBCOMMAND = "upload-file"

# 1 KeyboardInterrupt raised per this many seconds, no matter how many SIGINTs we get.
SIGINT_RATELIMIT = 0.5

last_signal_ts: Optional[float] = None


def sigint_handler(sig: int, frame: Optional[FrameType]) -> None:
global last_signal_ts
ts = time.monotonic()
# no need for atomicity here: we can't get another SIGINT before this one returns.
# https://www.gnu.org/software/libc/manual/html_node/Signals-in-Handler.html#Signals-in-Handler
if last_signal_ts is None or ts > last_signal_ts + SIGINT_RATELIMIT:
last_signal_ts = ts
raise KeyboardInterrupt


class GProfiler:
def __init__(
Expand Down Expand Up @@ -212,11 +197,7 @@ def _update_last_output(self, last_output_name: str, output_path: str) -> None:
atomically_symlink(os.path.basename(output_path), last_output)
# delete if rotating & there was a link target before.
if self._rotating_output and os.path.basename(prev_output) != last_output_name:
# can't use missing_ok=True, available only from 3.8 :/
try:
prev_output.unlink()
except FileNotFoundError:
pass
prev_output.unlink(missing_ok=True)

def _generate_output_files(
self,
Expand Down Expand Up @@ -445,7 +426,7 @@ def _submit_profile_logged(
logger.exception("Error occurred sending profile to server")
else:
logger.info("Successfully uploaded profiling data to the server")
return response_dict.get("gpid", "")
return cast(str, response_dict.get("gpid", ""))
return ""


Expand Down Expand Up @@ -955,17 +936,6 @@ def verify_preconditions(args: configargparse.Namespace, processes_to_profile: O
sys.exit(1)


def setup_signals() -> None:
# When we run under staticx & PyInstaller, both of them forward (some of the) signals to gProfiler.
# We catch SIGINTs and ratelimit them, to avoid being interrupted again during the handling of the
# first INT.
# See my commit message for more information.
signal.signal(signal.SIGINT, sigint_handler)
# handle SIGTERM in the same manner - gracefully stop gProfiler.
# SIGTERM is also forwarded by staticx & PyInstaller, so we need to ratelimit it.
signal.signal(signal.SIGTERM, sigint_handler)


def log_system_info() -> None:
system_info = get_static_system_info()
logger.info(f"gProfiler Python version: {system_info.python_version}")
Expand Down
14 changes: 7 additions & 7 deletions gprofiler/metadata/py_module_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def _get_packages_dir(file_path: str) -> Optional[str]:
def _get_metadata(dist: pkg_resources.Distribution) -> Dict[str, str]:
"""Based on pip._internal.utils.get_metadata"""
metadata_name = "METADATA"
if isinstance(dist, pkg_resources.DistInfoDistribution) and dist.has_metadata(metadata_name): # type: ignore
if isinstance(dist, pkg_resources.DistInfoDistribution) and dist.has_metadata(metadata_name):
metadata = dist.get_metadata(metadata_name)
elif dist.has_metadata("PKG-INFO"):
metadata_name = "PKG-INFO"
Expand Down Expand Up @@ -120,7 +120,7 @@ def _files_from_legacy(dist: pkg_resources.Distribution) -> Optional[Iterator[st
return None
paths = (p for p in text.splitlines(keepends=False) if p)
root = dist.location
info = dist.egg_info # type: ignore
info = dist.egg_info
if root is None or info is None:
return paths
try:
Expand Down Expand Up @@ -158,8 +158,8 @@ def _get_libpython_path(process: Process) -> Optional[str]:


# Matches PY_VERSION in Python's binary
# Notice that we'll match only 2.7 and 3.5-3.12
_PY_VERSION_STRING_PATTERN = re.compile(rb"(?<=\D)(?:2\.7|3\.(?:[5-9]|1[0-2]))\.\d\d?(?=\x00)")
# Notice that we'll match only 2.7 and 3.5-3.13
_PY_VERSION_STRING_PATTERN = re.compile(rb"(?<=\D)(?:2\.7|3\.(?:[5-9]|1[0-3]))\.\d\d?(?=\x00)")


@functools.lru_cache(maxsize=128)
Expand Down Expand Up @@ -230,8 +230,8 @@ def _populate_packages_versions(packages_versions: Dict[str, Optional[Tuple[str,
# This function resolves symlinks and makes paths absolute for comparison purposes which isn't required
# for our usage.
if hasattr(pkg_resources, "_normalize_cached"):
original__normalize_cache = pkg_resources._normalize_cached # type: ignore
pkg_resources._normalize_cached = lambda path: path # type: ignore
original__normalize_cache = pkg_resources._normalize_cached
pkg_resources._normalize_cached = lambda path: path
else:
global _warned_no__normalized_cached
if not _warned_no__normalized_cached:
Expand Down Expand Up @@ -261,7 +261,7 @@ def _populate_packages_versions(packages_versions: Dict[str, Optional[Tuple[str,
packages_versions[module_path] = package_info
finally:
# Don't forget to restore the original implementation in case someone else uses this function
pkg_resources._normalize_cached = original__normalize_cache # type: ignore
pkg_resources._normalize_cached = original__normalize_cache


_exceptions_logged = 0
Expand Down
10 changes: 7 additions & 3 deletions gprofiler/metadata/system_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ def decode_libc_version(version: bytes) -> str:

try:
ldd_version = run_process(
["ldd", "--version"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, suppress_log=True, check=False
["ldd", "--version"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
suppress_log=True,
check=False,
pdeathsigger=False,
).stdout
except FileNotFoundError:
ldd_version = b"ldd not found"
Expand Down Expand Up @@ -259,8 +264,7 @@ def get_static_system_info() -> SystemInfo:
processors=cpu_count,
cpu_model_name=cpu_model_name,
cpu_flags=cpu_flags,
memory_capacity_mb=round(psutil.virtual_memory().total / 1024 / 1024), # type: ignore # virtual_memory doesn't
# have a return type is types-psutil
memory_capacity_mb=round(psutil.virtual_memory().total / 1024 / 1024),
hostname=hostname,
system=platform.system(),
os_name=os_name,
Expand Down
4 changes: 3 additions & 1 deletion gprofiler/metadata/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ def get_exe_version(
exe_path = f"/proc/{get_process_nspid(process.pid)}/exe"

def _run_get_version() -> "CompletedProcess[bytes]":
return run_process([exe_path, version_arg], stop_event=stop_event, timeout=get_version_timeout)
return run_process(
[exe_path, version_arg], stop_event=stop_event, timeout=get_version_timeout, pdeathsigger=False
)

try:
cp = run_in_ns_wrapper(["pid", "mnt"], _run_get_version, process.pid)
Expand Down
5 changes: 3 additions & 2 deletions gprofiler/profilers/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from subprocess import CompletedProcess
from threading import Event, Lock
from types import TracebackType
from typing import Any, Dict, Iterable, List, Optional, Set, Type, TypeVar, Union, cast
from typing import Any, Dict, Iterable, List, Optional, Set, Type, TypeVar, Union

import psutil
from granulate_utils.java import (
Expand Down Expand Up @@ -350,11 +350,12 @@ def get_java_version(process: Process, stop_event: Event) -> Optional[str]:
def _run_java_version() -> "CompletedProcess[bytes]":
return run_process(
[
cast(str, process_java_path),
process_java_path,
"-version",
],
stop_event=stop_event,
timeout=_JAVA_VERSION_TIMEOUT,
pdeathsigger=False,
)

# doesn't work without changing PID NS as well (I'm getting ENOENT for libjli.so)
Expand Down
Loading
Loading