Skip to content

Commit 949319d

Browse files
authored
python: Get Python version in app metadata for uwsgi apps (#472)
Closes: #434
1 parent d867f70 commit 949319d

File tree

2 files changed

+40
-34
lines changed

2 files changed

+40
-34
lines changed

gprofiler/metadata/application_metadata.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def _clear_cache(self) -> None:
3737
if not is_process_running(process):
3838
del self._cache[process]
3939

40-
def get_exe_version(self, process: Process, version_arg: str = "--version") -> str:
40+
def get_exe_version(self, process: Process, version_arg: str = "--version", try_stderr: bool = False) -> str:
4141
"""
4242
Runs {process.exe()} --version in the appropriate namespace
4343
"""
@@ -46,11 +46,17 @@ def get_exe_version(self, process: Process, version_arg: str = "--version") -> s
4646
def _run_get_version() -> "CompletedProcess[bytes]":
4747
return run_process([exe_path, version_arg], stop_event=self._stop_event, timeout=self._GET_VERSION_TIMEOUT)
4848

49-
return run_in_ns(["pid", "mnt"], _run_get_version, process.pid).stdout.decode().strip()
49+
cp = run_in_ns(["pid", "mnt"], _run_get_version, process.pid)
50+
stdout = cp.stdout.decode().strip()
51+
# return stderr if stdout is empty, some apps print their version to stderr.
52+
if try_stderr and not stdout:
53+
return cp.stderr.decode().strip()
54+
55+
return stdout
5056

5157
@functools.lru_cache(4096)
52-
def get_exe_version_cached(self, process: Process, version_arg: str = "--version") -> str:
53-
return self.get_exe_version(process, version_arg)
58+
def get_exe_version_cached(self, process: Process, version_arg: str = "--version", try_stderr: bool = False) -> str:
59+
return self.get_exe_version(process, version_arg, try_stderr)
5460

5561
def get_metadata(self, process: Process) -> Optional[Dict]:
5662
metadata = self._cache.get(process)

gprofiler/profilers/python.py

+30-30
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from pathlib import Path
1212
from subprocess import CompletedProcess, Popen
1313
from threading import Event
14-
from typing import Any, Dict, List, Match, NoReturn, Optional, Tuple, cast
14+
from typing import Any, Dict, List, Match, NoReturn, Optional, cast
1515

1616
from granulate_utils.linux.elf import get_elf_id
1717
from granulate_utils.linux.ns import get_process_nspid, is_running_in_init_pid, run_in_ns
@@ -94,43 +94,43 @@ def _add_versions_to_stacks(
9494

9595

9696
class PythonMetadata(ApplicationMetadata):
97-
_PYTHON_VERSION_TIMEOUT = 3
98-
99-
def _run_process_python(self, process: Process, args: List[str]) -> Tuple[str, str]:
100-
if not is_process_basename_matching(process, application_identifiers._PYTHON_BIN_RE):
101-
# TODO: for dynamic executables, find the python binary that works with the loaded libpython, and
102-
# check it instead. For static executables embedding libpython - :shrug:
103-
raise NotImplementedError
104-
105-
python_path = f"/proc/{get_process_nspid(process.pid)}/exe"
106-
107-
def _run_python_process_in_ns() -> "CompletedProcess[bytes]":
108-
return run_process(
109-
[
110-
python_path,
111-
]
112-
+ args,
113-
stop_event=self._stop_event,
114-
timeout=self._PYTHON_VERSION_TIMEOUT,
115-
)
116-
117-
cp = run_in_ns(["pid", "mnt"], _run_python_process_in_ns, process.pid)
118-
return cp.stdout.decode().strip(), cp.stderr.decode().strip()
97+
_PYTHON_TIMEOUT = 3
11998

12099
def _get_python_version(self, process: Process) -> Optional[str]:
121100
try:
122-
stdout, stderr = self._run_process_python(process, ["-V"])
123-
if stdout:
124-
return stdout
125-
# Python 2 prints -V to stderr, so return that instead.
126-
return stderr
101+
if is_process_basename_matching(process, application_identifiers._PYTHON_BIN_RE):
102+
version_arg = "-V"
103+
prefix = ""
104+
elif is_process_basename_matching(process, r"^uwsgi$"):
105+
version_arg = "--python-version"
106+
# for compatibility, we add this prefix (to match python -V)
107+
prefix = "Python "
108+
else:
109+
# TODO: for dynamic executables, find the python binary that works with the loaded libpython, and
110+
# check it instead. For static executables embedding libpython - :shrug:
111+
raise NotImplementedError
112+
113+
# Python 2 prints -V to stderr, so try that as well.
114+
return prefix + self.get_exe_version_cached(process, version_arg=version_arg, try_stderr=True)
127115
except Exception:
128116
return None
129117

130118
def _get_sys_maxunicode(self, process: Process) -> Optional[str]:
131119
try:
132-
stdout, stderr = self._run_process_python(process, ["-S", "-c", "import sys; print(sys.maxunicode)"])
133-
return stdout
120+
if not is_process_basename_matching(process, application_identifiers._PYTHON_BIN_RE):
121+
# see same raise above
122+
raise NotImplementedError
123+
124+
python_path = f"/proc/{get_process_nspid(process.pid)}/exe"
125+
126+
def _run_python_process_in_ns() -> "CompletedProcess[bytes]":
127+
return run_process(
128+
[python_path, "-S", "-c", "import sys; print(sys.maxunicode)"],
129+
stop_event=self._stop_event,
130+
timeout=self._PYTHON_TIMEOUT,
131+
)
132+
133+
return run_in_ns(["pid", "mnt"], _run_python_process_in_ns, process.pid).stdout.decode().strip()
134134
except Exception:
135135
return None
136136

0 commit comments

Comments
 (0)