diff --git a/granulate_utils/golang.py b/granulate_utils/golang.py new file mode 100644 index 00000000..719633cd --- /dev/null +++ b/granulate_utils/golang.py @@ -0,0 +1,43 @@ +# +# Copyright (c) Granulate. All rights reserved. +# Licensed under the AGPL3 License. See LICENSE.md in the project root for license information. +# + +import functools +import struct +from typing import Optional + +from psutil import NoSuchProcess, Process + +from granulate_utils.linux.elf import read_elf_symbol, read_elf_va +from granulate_utils.linux.process import process_exe + + +def is_golang_process(process: Process) -> bool: + return get_process_golang_version(process) is not None + + +@functools.lru_cache(maxsize=4096) +def get_process_golang_version(process: Process) -> Optional[str]: + elf_path = process_exe(process) + try: + symbol_data = read_elf_symbol(elf_path, "runtime.buildVersion", 16) + except FileNotFoundError: + raise NoSuchProcess(process.pid) + if symbol_data is None: + return None + + # Declaration of go string type: + # type stringStruct struct { + # str unsafe.Pointer + # len int + # } + addr, length = struct.unpack("QQ", symbol_data) + try: + golang_version_bytes = read_elf_va(elf_path, addr, length) + except FileNotFoundError: + raise NoSuchProcess(process.pid) + if golang_version_bytes is None: + return None + + return golang_version_bytes.decode() diff --git a/granulate_utils/linux/process.py b/granulate_utils/linux/process.py index f90de336..82152fd0 100644 --- a/granulate_utils/linux/process.py +++ b/granulate_utils/linux/process.py @@ -3,9 +3,11 @@ # Licensed under the AGPL3 License. See LICENSE.md in the project root for license information. # import os +import re import struct from contextlib import contextmanager from typing import Generator, Optional +from functools import lru_cache import psutil @@ -113,3 +115,16 @@ def _translate_errors(process: psutil.Process) -> Generator[None, None, None]: if not os.path.exists(f"/proc/{process.pid}"): raise psutil.NoSuchProcess(process.pid) raise + + +@lru_cache(maxsize=512) +def is_process_basename_matching(process: psutil.Process, basename_pattern: str) -> bool: + if re.match(basename_pattern, os.path.basename(process_exe(process))): + return True + + # process was executed AS basename (but has different exe name) + cmd = process.cmdline() + if len(cmd) > 0 and re.match(basename_pattern, cmd[0]): + return True + + return False diff --git a/granulate_utils/node.py b/granulate_utils/node.py new file mode 100644 index 00000000..45344820 --- /dev/null +++ b/granulate_utils/node.py @@ -0,0 +1,12 @@ +# +# Copyright (c) Granulate. All rights reserved. +# Licensed under the AGPL3 License. See LICENSE.md in the project root for license information. +# + +from psutil import Process + +from granulate_utils.linux.process import is_process_basename_matching + + +def is_node_process(process: Process) -> bool: + return is_process_basename_matching(process, r"^node$")