summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/telemetry.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozbuild/mozbuild/telemetry.py')
-rw-r--r--python/mozbuild/mozbuild/telemetry.py264
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