#!/usr/bin/env python3 # Copyright 2024 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import argparse import json import logging import os import subprocess import sys import textwrap import utils _DEFAULT_CONFIG_PATH = utils.depot_tools_config_path("build_telemetry.cfg") _DEFAULT_COUNTDOWN = 10 VERSION = 1 class Config: def __init__(self, config_path, countdown): self._config_path = config_path self._config = None self._notice_displayed = False self._countdown = countdown def load(self): """Loads the build telemetry config.""" if self._config: return config = {} if os.path.isfile(self._config_path): with open(self._config_path) as f: try: config = json.load(f) except Exception: pass if config.get("version") != VERSION: config = None # Reset the state for version change. if not config: config = { "user": check_auth().get("email", ""), "status": None, "countdown": self._countdown, "version": VERSION, } if not config.get("user"): config["user"] = check_auth().get("email", "") self._config = config def save(self): with open(self._config_path, "w") as f: json.dump(self._config, f) @property def path(self): return self._config_path @property def is_googler(self): return self.user.endswith("@google.com") @property def user(self): if not self._config: return return self._config.get("user", "") @property def countdown(self): if not self._config: return return self._config.get("countdown") @property def version(self): if not self._config: return return self._config.get("version") def enabled(self): if not self._config: print("WARNING: depot_tools.build_telemetry: %s is not loaded." % self._config_path, file=sys.stderr) return False if not self.is_googler: return False if self._config.get("status") == "opt-out": return False if self._should_show_notice(): remaining = max(0, self._config["countdown"] - 1) self._show_notice(remaining) self._notice_displayed = True self._config["countdown"] = remaining self.save() # Telemetry collection will happen. return True def _should_show_notice(self): if self._notice_displayed: return False if self._config.get("countdown") == 0: return False if self._config.get("status") == "opt-in": return False return True def _show_notice(self, remaining): """Dispalys notice when necessary.""" print( textwrap.dedent(f"""\ *** NOTICE *** Google-internal telemetry (including build logs, username, and hostname) is collected on corp machines to diagnose performance and fix build issues. This reminder will be shown {remaining} more times. See http://go/chrome-build-telemetry for details. Hide this notice or opt out by running: build_telemetry [opt-in] [opt-out] *** END NOTICE *** """)) def opt_in(self): self._config["status"] = "opt-in" self.save() print("build telemetry collection is opted in") def opt_out(self): self._config["status"] = "opt-out" self.save() print("build telemetry collection is opted out") def status(self): return self._config["status"] def load_config(cfg_path=_DEFAULT_CONFIG_PATH, countdown=_DEFAULT_COUNTDOWN): """Loads the config from the default location.""" cfg = Config(cfg_path, countdown) cfg.load() return cfg def check_auth(): """Checks auth information.""" try: out = subprocess.check_output( "cipd auth-info --json-output -", text=True, shell=True, stderr=subprocess.DEVNULL, timeout=3, ) except Exception as e: return {} try: return json.loads(out) except json.JSONDecodeError as e: logging.error(e) return {} def enabled(): """Checks whether the build can upload build telemetry.""" cfg = load_config() return cfg.enabled() def print_status(cfg): status = cfg.status() if status == "opt-in": print("build telemetry collection is enabled. You have opted in.") elif status == "opt-out": print("build telemetry collection is disabled. You have opted out.") else: print("build telemetry collection is enabled.") print("") def main(): parser = argparse.ArgumentParser(prog="build_telemetry") parser.add_argument('status', nargs='?', choices=["opt-in", "opt-out", "status"]) args = parser.parse_args() cfg = load_config() if not cfg.is_googler: cfg.save() return if args.status == "opt-in": cfg.opt_in() return if args.status == "opt-out": cfg.opt_out() return if args.status == "status": print_status(cfg) return print_status(cfg) parser.print_help() if __name__ == "__main__": sys.exit(main())