| # Copyright 2023 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Defines the ResourceDetector to capture resource properties. |
| |
| Taken from the implementation for chromite with portage specific additions. |
| """ |
| |
| import getpass |
| import logging |
| import os |
| from pathlib import Path |
| import platform |
| import sys |
| from typing import Sequence |
| |
| from opentelemetry.sdk import resources |
| |
| import portage |
| |
| CPU_ARCHITECTURE = "cpu.architecture" |
| CPU_NAME = "cpu.name" |
| CPU_COUNT = "cpu.count" |
| HOST_TYPE = "host.type" |
| MEMORY_SWAP_TOTAL = "memory.swap.total" |
| MEMORY_TOTAL = "memory.total" |
| PROCESS_CWD = "process.cwd" |
| PROCESS_RUNTIME_API_VERSION = "process.runtime.apiversion" |
| PROCESS_ENV = "process.env" |
| OS_NAME = "os.name" |
| DMI_PATH = Path("/sys/class/dmi/id/product_name") |
| GCE_DMI = "Google Compute Engine" |
| CROS_BOT_USER = "chrome-bot" |
| PROC_MEMINFO_PATH = Path("/proc/meminfo") |
| |
| |
| class ProcessDetector(resources.ResourceDetector): |
| """ResourceDetector to capture information about the process.""" |
| |
| def __init__(self, allowed_env: Sequence[str] = None): |
| super().__init__() |
| self._allowed_env = allowed_env or ["USE"] |
| |
| def detect(self) -> resources.Resource: |
| env = os.environ |
| resource = { |
| PROCESS_CWD: os.getcwd(), |
| PROCESS_RUNTIME_API_VERSION: sys.api_version, |
| resources.PROCESS_PID: os.getpid(), |
| resources.PROCESS_OWNER: os.geteuid(), |
| resources.PROCESS_EXECUTABLE_NAME: Path(sys.executable).name, |
| resources.PROCESS_EXECUTABLE_PATH: sys.executable, |
| resources.PROCESS_COMMAND: sys.argv[0], |
| resources.PROCESS_COMMAND_ARGS: sys.argv[1:], |
| resources.SERVICE_NAME: "portage", |
| resources.SERVICE_VERSION: portage.VERSION, |
| } |
| resource.update( |
| { |
| f"{PROCESS_ENV}.{k}": env[k] |
| for k in self._allowed_env |
| if k in env |
| } |
| ) |
| |
| return resources.Resource(resource) |
| |
| |
| class SystemDetector(resources.ResourceDetector): |
| """ResourceDetector to capture information about system.""" |
| |
| def detect(self) -> resources.Resource: |
| host_type = "UNKNOWN" |
| |
| if DMI_PATH.exists(): |
| host_type = DMI_PATH.read_text(encoding="utf-8") |
| |
| if host_type == GCE_DMI and getpass.getuser() == CROS_BOT_USER: |
| host_type = "chromeos-bot" |
| |
| mem_info = MemoryInfo() |
| |
| resource = { |
| CPU_ARCHITECTURE: platform.machine(), |
| CPU_COUNT: os.cpu_count(), |
| CPU_NAME: platform.processor(), |
| HOST_TYPE: host_type.strip(), |
| MEMORY_SWAP_TOTAL: mem_info.total_swap_memory, |
| MEMORY_TOTAL: mem_info.total_physical_ram, |
| OS_NAME: os.name, |
| resources.OS_TYPE: platform.system(), |
| resources.OS_DESCRIPTION: platform.platform(), |
| } |
| |
| return resources.Resource(resource) |
| |
| |
| class MemoryInfo: |
| """Read machine memory info from /proc/meminfo.""" |
| |
| # Prefixes for the /proc/meminfo file that we care about. |
| MEMINFO_VIRTUAL_MEMORY_TOTAL = "VmallocTotal" |
| MEMINFO_PHYSICAL_RAM_TOTAL = "MemTotal" |
| MEMINFO_SWAP_MEMORY_TOTAL = "SwapTotal" |
| |
| def __init__(self): |
| self._total_physical_ram = 0 |
| self._total_virtual_memory = 0 |
| self._total_swap_memory = 0 |
| try: |
| contents = PROC_MEMINFO_PATH.read_text(encoding="utf-8") |
| except OSError as e: |
| logging.warning("Encountered an issue reading /proc/meminfo: %s", e) |
| return |
| |
| for line in contents.splitlines(): |
| if line.startswith(self.MEMINFO_SWAP_MEMORY_TOTAL): |
| self._total_swap_memory = self._get_mem_value(line) |
| elif line.startswith(self.MEMINFO_VIRTUAL_MEMORY_TOTAL): |
| self._total_virtual_memory = self._get_mem_value(line) |
| elif line.startswith(self.MEMINFO_PHYSICAL_RAM_TOTAL): |
| self._total_physical_ram = self._get_mem_value(line) |
| |
| @property |
| def total_physical_ram(self) -> int: |
| return self._total_physical_ram |
| |
| @property |
| def total_virtual_memory(self) -> int: |
| return self._total_virtual_memory |
| |
| @property |
| def total_swap_memory(self) -> int: |
| return self._total_swap_memory |
| |
| def _get_mem_value(self, line: str) -> int: |
| """Reads an individual line from /proc/meminfo and returns the size. |
| |
| This function also converts the read value from kibibytes to bytes |
| when the read value has a unit provided for memory size. |
| |
| The specification information for /proc files, including meminfo, can |
| be found at |
| https://www.kernel.org/doc/Documentation/filesystems/proc.txt. |
| |
| Args: |
| line: The text line read from /proc/meminfo. |
| |
| Returns: |
| The integer value after conversion. |
| """ |
| components = line.split() |
| if len(components) == 1: |
| logging.warning( |
| "Unexpected /proc/meminfo entry with no label:number value was " |
| "provided. Value read: '%s'", |
| line, |
| ) |
| return 0 |
| size = int(components[1]) |
| if len(components) == 2: |
| return size |
| # The RHEL and kernel.org specs for /proc/meminfo doesn't give any |
| # indication that a memory unit besides kB (kibibytes) is expected, |
| # except in the cases of page counts, where no unit is provided. |
| if components[2] != "kB": |
| logging.warning( |
| "Unit for memory consumption in /proc/meminfo does " |
| "not conform to expectations. Please review the " |
| "read value: %s", |
| line, |
| ) |
| return size * 1024 |