build_telemetry.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. #!/usr/bin/env python3
  2. # Copyright 2024 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import argparse
  6. import json
  7. import logging
  8. import os
  9. import subprocess
  10. import sys
  11. import textwrap
  12. import utils
  13. _DEFAULT_CONFIG_PATH = utils.depot_tools_config_path("build_telemetry.cfg")
  14. _DEFAULT_COUNTDOWN = 10
  15. VERSION = 1
  16. class Config:
  17. def __init__(self, config_path, countdown):
  18. self._config_path = config_path
  19. self._config = None
  20. self._notice_displayed = False
  21. self._countdown = countdown
  22. def load(self):
  23. """Loads the build telemetry config."""
  24. if self._config:
  25. return
  26. config = {}
  27. if os.path.isfile(self._config_path):
  28. with open(self._config_path) as f:
  29. try:
  30. config = json.load(f)
  31. except Exception:
  32. pass
  33. if config.get("version") != VERSION:
  34. config = None # Reset the state for version change.
  35. if not config:
  36. config = {
  37. "user": check_auth().get("email", ""),
  38. "status": None,
  39. "countdown": self._countdown,
  40. "version": VERSION,
  41. }
  42. if not config.get("user"):
  43. config["user"] = check_auth().get("email", "")
  44. self._config = config
  45. def save(self):
  46. with open(self._config_path, "w") as f:
  47. json.dump(self._config, f)
  48. @property
  49. def path(self):
  50. return self._config_path
  51. @property
  52. def is_googler(self):
  53. return self.user.endswith("@google.com")
  54. @property
  55. def user(self):
  56. if not self._config:
  57. return
  58. return self._config.get("user", "")
  59. @property
  60. def countdown(self):
  61. if not self._config:
  62. return
  63. return self._config.get("countdown")
  64. @property
  65. def version(self):
  66. if not self._config:
  67. return
  68. return self._config.get("version")
  69. def enabled(self):
  70. if not self._config:
  71. print("WARNING: depot_tools.build_telemetry: %s is not loaded." %
  72. self._config_path,
  73. file=sys.stderr)
  74. return False
  75. if not self.is_googler:
  76. return False
  77. if self._config.get("status") == "opt-out":
  78. return False
  79. if self._should_show_notice():
  80. remaining = max(0, self._config["countdown"] - 1)
  81. self._show_notice(remaining)
  82. self._notice_displayed = True
  83. self._config["countdown"] = remaining
  84. self.save()
  85. # Telemetry collection will happen.
  86. return True
  87. def _should_show_notice(self):
  88. if self._notice_displayed:
  89. return False
  90. if self._config.get("countdown") == 0:
  91. return False
  92. if self._config.get("status") == "opt-in":
  93. return False
  94. return True
  95. def _show_notice(self, remaining):
  96. """Dispalys notice when necessary."""
  97. print(
  98. textwrap.dedent(f"""\
  99. *** NOTICE ***
  100. 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]
  101. *** END NOTICE ***
  102. """))
  103. def opt_in(self):
  104. self._config["status"] = "opt-in"
  105. self.save()
  106. print("build telemetry collection is opted in")
  107. def opt_out(self):
  108. self._config["status"] = "opt-out"
  109. self.save()
  110. print("build telemetry collection is opted out")
  111. def status(self):
  112. return self._config["status"]
  113. def load_config(cfg_path=_DEFAULT_CONFIG_PATH, countdown=_DEFAULT_COUNTDOWN):
  114. """Loads the config from the default location."""
  115. cfg = Config(cfg_path, countdown)
  116. cfg.load()
  117. return cfg
  118. def check_auth():
  119. """Checks auth information."""
  120. try:
  121. out = subprocess.check_output(
  122. "cipd auth-info --json-output -",
  123. text=True,
  124. shell=True,
  125. stderr=subprocess.DEVNULL,
  126. timeout=3,
  127. )
  128. except Exception as e:
  129. return {}
  130. try:
  131. return json.loads(out)
  132. except json.JSONDecodeError as e:
  133. logging.error(e)
  134. return {}
  135. def enabled():
  136. """Checks whether the build can upload build telemetry."""
  137. cfg = load_config()
  138. return cfg.enabled()
  139. def print_status(cfg):
  140. status = cfg.status()
  141. if status == "opt-in":
  142. print("build telemetry collection is enabled. You have opted in.")
  143. elif status == "opt-out":
  144. print("build telemetry collection is disabled. You have opted out.")
  145. else:
  146. print("build telemetry collection is enabled.")
  147. print("")
  148. def main():
  149. parser = argparse.ArgumentParser(prog="build_telemetry")
  150. parser.add_argument('status',
  151. nargs='?',
  152. choices=["opt-in", "opt-out", "status"])
  153. args = parser.parse_args()
  154. cfg = load_config()
  155. if not cfg.is_googler:
  156. cfg.save()
  157. return
  158. if args.status == "opt-in":
  159. cfg.opt_in()
  160. return
  161. if args.status == "opt-out":
  162. cfg.opt_out()
  163. return
  164. if args.status == "status":
  165. print_status(cfg)
  166. return
  167. print_status(cfg)
  168. parser.print_help()
  169. if __name__ == "__main__":
  170. sys.exit(main())