1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5from __future__ import division, absolute_import, print_function, unicode_literals 6 7""" 8This file contains functions used for telemetry. 9""" 10 11import distro 12import os 13import math 14import platform 15import sys 16 17import mozpack.path as mozpath 18from .base import BuildEnvironmentNotFoundException 19 20 21def cpu_brand_linux(): 22 """ 23 Read the CPU brand string out of /proc/cpuinfo on Linux. 24 """ 25 with open("/proc/cpuinfo", "r") as f: 26 for line in f: 27 if line.startswith("model name"): 28 _, brand = line.split(": ", 1) 29 return brand.rstrip() 30 # not found? 31 return None 32 33 34def cpu_brand_windows(): 35 """ 36 Read the CPU brand string from the registry on Windows. 37 """ 38 try: 39 import _winreg 40 except ImportError: 41 import winreg as _winreg 42 43 try: 44 h = _winreg.OpenKey( 45 _winreg.HKEY_LOCAL_MACHINE, 46 r"HARDWARE\DESCRIPTION\System\CentralProcessor\0", 47 ) 48 (brand, ty) = _winreg.QueryValueEx(h, "ProcessorNameString") 49 if ty == _winreg.REG_SZ: 50 return brand 51 except WindowsError: 52 pass 53 return None 54 55 56def cpu_brand_mac(): 57 """ 58 Get the CPU brand string via sysctl on macos. 59 """ 60 import ctypes 61 import ctypes.util 62 63 libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("c")) 64 # First, find the required buffer size. 65 bufsize = ctypes.c_size_t(0) 66 result = libc.sysctlbyname( 67 b"machdep.cpu.brand_string", None, ctypes.byref(bufsize), None, 0 68 ) 69 if result != 0: 70 return None 71 bufsize.value += 1 72 buf = ctypes.create_string_buffer(bufsize.value) 73 # Now actually get the value. 74 result = libc.sysctlbyname( 75 b"machdep.cpu.brand_string", buf, ctypes.byref(bufsize), None, 0 76 ) 77 if result != 0: 78 return None 79 80 return buf.value.decode() 81 82 83def get_cpu_brand(): 84 """ 85 Get the CPU brand string as returned by CPUID. 86 """ 87 return { 88 "Linux": cpu_brand_linux, 89 "Windows": cpu_brand_windows, 90 "Darwin": cpu_brand_mac, 91 }.get(platform.system(), lambda: None)() 92 93 94def get_os_name(): 95 return {"Linux": "linux", "Windows": "windows", "Darwin": "macos"}.get( 96 platform.system(), "other" 97 ) 98 99 100def get_psutil_stats(): 101 """Return whether psutil exists and its associated stats. 102 103 @returns (bool, int, int, int) whether psutil exists, the logical CPU count, 104 physical CPU count, and total number of bytes of memory. 105 """ 106 try: 107 import psutil 108 109 return ( 110 True, 111 psutil.cpu_count(), 112 psutil.cpu_count(logical=False), 113 psutil.virtual_memory().total, 114 ) 115 except ImportError: 116 return False, None, None, None 117 118 119def get_system_info(): 120 """ 121 Gather info to fill the `system` keys in the schema. 122 """ 123 # Normalize OS names a bit, and bucket non-tier-1 platforms into "other". 124 has_psutil, logical_cores, physical_cores, memory_total = get_psutil_stats() 125 info = {"os": get_os_name()} 126 if has_psutil: 127 # `total` on Linux is gathered from /proc/meminfo's `MemTotal`, which is the 128 # total amount of physical memory minus some kernel usage, so round up to the 129 # nearest GB to get a sensible answer. 130 info["memory_gb"] = int(math.ceil(float(memory_total) / (1024 * 1024 * 1024))) 131 info["logical_cores"] = logical_cores 132 if physical_cores is not None: 133 info["physical_cores"] = physical_cores 134 cpu_brand = get_cpu_brand() 135 if cpu_brand is not None: 136 info["cpu_brand"] = cpu_brand 137 # TODO: drive_is_ssd, virtual_machine: https://bugzilla.mozilla.org/show_bug.cgi?id=1481613 138 return info 139 140 141def get_build_opts(substs): 142 """ 143 Translate selected items from `substs` into `build_opts` keys in the schema. 144 """ 145 try: 146 opts = { 147 k: ty(substs.get(s, None)) 148 for (k, s, ty) in ( 149 # Selected substitutions. 150 ("artifact", "MOZ_ARTIFACT_BUILDS", bool), 151 ("debug", "MOZ_DEBUG", bool), 152 ("opt", "MOZ_OPTIMIZE", bool), 153 ("ccache", "CCACHE", bool), 154 ("sccache", "MOZ_USING_SCCACHE", bool), 155 ) 156 } 157 compiler = substs.get("CC_TYPE", None) 158 if compiler: 159 opts["compiler"] = str(compiler) 160 if substs.get("CXX_IS_ICECREAM", None): 161 opts["icecream"] = True 162 return opts 163 except BuildEnvironmentNotFoundException: 164 return {} 165 166 167def get_build_attrs(attrs): 168 """ 169 Extracts clobber and cpu usage info from command attributes. 170 """ 171 res = {} 172 clobber = attrs.get("clobber") 173 if clobber: 174 res["clobber"] = clobber 175 usage = attrs.get("usage") 176 if usage: 177 cpu_percent = usage.get("cpu_percent") 178 if cpu_percent: 179 res["cpu_percent"] = int(round(cpu_percent)) 180 return res 181 182 183def filter_args(command, argv, topsrcdir, topobjdir, cwd=None): 184 """ 185 Given the full list of command-line arguments, remove anything up to and including `command`, 186 and attempt to filter absolute pathnames out of any arguments after that. 187 """ 188 if cwd is None: 189 cwd = os.getcwd() 190 191 # Each key is a pathname and the values are replacement sigils 192 paths = { 193 topsrcdir: "$topsrcdir/", 194 topobjdir: "$topobjdir/", 195 mozpath.normpath(os.path.expanduser("~")): "$HOME/", 196 # This might override one of the existing entries, that's OK. 197 # We don't use a sigil here because we treat all arguments as potentially relative 198 # paths, so we'd like to get them back as they were specified. 199 mozpath.normpath(cwd): "", 200 } 201 202 args = list(argv) 203 while args: 204 a = args.pop(0) 205 if a == command: 206 break 207 208 def filter_path(p): 209 p = mozpath.abspath(p) 210 base = mozpath.basedir(p, paths.keys()) 211 if base: 212 return paths[base] + mozpath.relpath(p, base) 213 # Best-effort. 214 return "<path omitted>" 215 216 return [filter_path(arg) for arg in args] 217 218 219def get_distro_and_version(): 220 if sys.platform.startswith("linux"): 221 dist, version, _ = distro.linux_distribution(full_distribution_name=False) 222 return dist, version 223 elif sys.platform.startswith("darwin"): 224 return "macos", platform.mac_ver()[0] 225 elif sys.platform.startswith("win32") or sys.platform.startswith("msys"): 226 ver = sys.getwindowsversion() 227 return "windows", "%s.%s.%s" % (ver.major, ver.minor, ver.build) 228 else: 229 return sys.platform, "" 230 231 232def get_shell_info(): 233 """Returns if the current shell was opened by vscode and if it's a SSH connection""" 234 235 return ( 236 True if "vscode" in os.getenv("TERM_PROGRAM", "") else False, 237 bool(os.getenv("SSH_CLIENT", False)), 238 ) 239