build_telemetry.py 5.5 KB

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