diff options
Diffstat (limited to 'python/mozbuild/mozbuild/telemetry.py')
-rw-r--r-- | python/mozbuild/mozbuild/telemetry.py | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/telemetry.py b/python/mozbuild/mozbuild/telemetry.py new file mode 100644 index 0000000000..d656a9a2aa --- /dev/null +++ b/python/mozbuild/mozbuild/telemetry.py @@ -0,0 +1,264 @@ +# 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 "<path omitted>" + + 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 |