# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """ This file contains functions used for telemetry. """ import math import os import platform import sys import distro import mozpack.path as mozpath from .base import BuildEnvironmentNotFoundException def cpu_brand_linux(): """ Read the CPU brand string out of /proc/cpuinfo on Linux. """ with open("/proc/cpuinfo", "r") as f: for line in f: if line.startswith("model name"): _, brand = line.split(": ", 1) return brand.rstrip() # not found? return None def cpu_brand_windows(): """ Read the CPU brand string from the registry on Windows. """ try: import _winreg except ImportError: import winreg as _winreg try: h = _winreg.OpenKey( _winreg.HKEY_LOCAL_MACHINE, r"HARDWARE\DESCRIPTION\System\CentralProcessor\0", ) (brand, ty) = _winreg.QueryValueEx(h, "ProcessorNameString") if ty == _winreg.REG_SZ: return brand except WindowsError: pass return None def cpu_brand_mac(): """ Get the CPU brand string via sysctl on macos. """ import ctypes import ctypes.util libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("c")) # First, find the required buffer size. bufsize = ctypes.c_size_t(0) result = libc.sysctlbyname( b"machdep.cpu.brand_string", None, ctypes.byref(bufsize), None, 0 ) if result != 0: return None bufsize.value += 1 buf = ctypes.create_string_buffer(bufsize.value) # Now actually get the value. result = libc.sysctlbyname( b"machdep.cpu.brand_string", buf, ctypes.byref(bufsize), None, 0 ) if result != 0: return None return buf.value.decode() def get_cpu_brand(): """ Get the CPU brand string as returned by CPUID. """ return { "Linux": cpu_brand_linux, "Windows": cpu_brand_windows, "Darwin": cpu_brand_mac, }.get(platform.system(), lambda: None)() def get_os_name(): return {"Linux": "linux", "Windows": "windows", "Darwin": "macos"}.get( platform.system(), "other" ) def get_psutil_stats(): """Return whether psutil exists and its associated stats. @returns (bool, int, int, int) whether psutil exists, the logical CPU count, physical CPU count, and total number of bytes of memory. """ try: import psutil return ( True, psutil.cpu_count(), psutil.cpu_count(logical=False), psutil.virtual_memory().total, ) except ImportError: return False, None, None, None def get_system_info(): """ Gather info to fill the `system` keys in the schema. """ # Normalize OS names a bit, and bucket non-tier-1 platforms into "other". has_psutil, logical_cores, physical_cores, memory_total = get_psutil_stats() info = {"os": get_os_name()} if has_psutil: # `total` on Linux is gathered from /proc/meminfo's `MemTotal`, which is the # total amount of physical memory minus some kernel usage, so round up to the # nearest GB to get a sensible answer. info["memory_gb"] = int(math.ceil(float(memory_total) / (1024 * 1024 * 1024))) info["logical_cores"] = logical_cores if physical_cores is not None: info["physical_cores"] = physical_cores cpu_brand = get_cpu_brand() if cpu_brand is not None: info["cpu_brand"] = cpu_brand # TODO: drive_is_ssd, virtual_machine: https://bugzilla.mozilla.org/show_bug.cgi?id=1481613 return info def get_build_opts(substs): """ Translate selected items from `substs` into `build_opts` keys in the schema. """ try: opts = { k: ty(substs.get(s, None)) for (k, s, ty) in ( # Selected substitutions. ("artifact", "MOZ_ARTIFACT_BUILDS", bool), ("debug", "MOZ_DEBUG", bool), ("opt", "MOZ_OPTIMIZE", bool), ("ccache", "CCACHE", bool), ("sccache", "MOZ_USING_SCCACHE", bool), ) } compiler = substs.get("CC_TYPE", None) if compiler: opts["compiler"] = str(compiler) if substs.get("CXX_IS_ICECREAM", None): opts["icecream"] = True return opts except BuildEnvironmentNotFoundException: return {} def get_build_attrs(attrs): """ Extracts clobber and cpu usage info from command attributes. """ res = {} clobber = attrs.get("clobber") if clobber: res["clobber"] = clobber usage = attrs.get("usage") if usage: cpu_percent = usage.get("cpu_percent") if cpu_percent: res["cpu_percent"] = int(round(cpu_percent)) return res def filter_args(command, argv, topsrcdir, topobjdir, cwd=None): """ Given the full list of command-line arguments, remove anything up to and including `command`, and attempt to filter absolute pathnames out of any arguments after that. """ if cwd is None: cwd = os.getcwd() # Each key is a pathname and the values are replacement sigils paths = { topsrcdir: "$topsrcdir/", topobjdir: "$topobjdir/", mozpath.normpath(os.path.expanduser("~")): "$HOME/", # This might override one of the existing entries, that's OK. # We don't use a sigil here because we treat all arguments as potentially relative # paths, so we'd like to get them back as they were specified. mozpath.normpath(cwd): "", } args = list(argv) while args: a = args.pop(0) if a == command: break def filter_path(p): p = mozpath.abspath(p) base = mozpath.basedir(p, paths.keys()) if base: return paths[base] + mozpath.relpath(p, base) # Best-effort. return "" return [filter_path(arg) for arg in args] def get_distro_and_version(): if sys.platform.startswith("linux"): dist, version, _ = distro.linux_distribution(full_distribution_name=False) return dist, version elif sys.platform.startswith("darwin"): return "macos", platform.mac_ver()[0] elif sys.platform.startswith("win32") or sys.platform.startswith("msys"): ver = sys.getwindowsversion() return "windows", "%s.%s.%s" % (ver.major, ver.minor, ver.build) else: return sys.platform, "" def get_shell_info(): """Returns if the current shell was opened by vscode and if it's a SSH connection""" return ( True if "vscode" in os.getenv("TERM_PROGRAM", "") else False, bool(os.getenv("SSH_CLIENT", False)), ) def get_vscode_running(): """Return if the vscode is currently running.""" try: import psutil for proc in psutil.process_iter(): try: # On Windows we have "Code.exe" # On MacOS we have "Code Helper (Renderer)" # On Linux we have "" if ( proc.name == "Code.exe" or proc.name == "Code Helper (Renderer)" or proc.name == "code" ): return True except Exception: # may not be able to access process info for all processes continue except Exception: # On some platforms, sometimes, the generator throws an # exception preventing us to enumerate. return False return False