From d495580f42169532e2b356bd36fb2a151194312e Mon Sep 17 00:00:00 2001 From: Struan Shrimpton Date: Wed, 2 Oct 2024 20:38:47 +0000 Subject: [PATCH] Add telemetry initialization and opt out utility This includes initializing the module which will handle printing the notice and enabling/disabling telemetry based on config. The __main__ file also allows for the banner to have a consistent entry point for disabling telemetry so instrumenting programs don't need to worry about it if they are using the same default config file. Bug: 326277821 Change-Id: I861d6b9311ed48c2e1effcac22b314b73e2641e3 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5901465 Reviewed-by: Terrence Reilly Commit-Queue: Struan Shrimpton --- infra_lib/telemetry/__init__.py | 122 ++++++++++++++++++++++++++++++++ infra_lib/telemetry/__main__.py | 41 +++++++++++ infra_lib/telemetry/config.py | 3 + 3 files changed, 166 insertions(+) create mode 100755 infra_lib/telemetry/__main__.py diff --git a/infra_lib/telemetry/__init__.py b/infra_lib/telemetry/__init__.py index e69de29bb..521aa6a4f 100644 --- a/infra_lib/telemetry/__init__.py +++ b/infra_lib/telemetry/__init__.py @@ -0,0 +1,122 @@ +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +from typing import Optional +import os +import socket +import sys +import pathlib + +from opentelemetry import context as otel_context_api +from opentelemetry import trace as otel_trace_api +from opentelemetry.sdk import ( + resources as otel_resources, + trace as otel_trace_sdk, +) +from opentelemetry.sdk.trace import export as otel_export +from opentelemetry.util import types as otel_types + +from . import config +from . import clearcut_span_exporter +from . import detector + +DEFAULT_BANNER = """ +=============================================================================== +To help improve the quality of this product, we collect usage data and +stacktraces from googlers. This includes a uuid generated weekly to identify +invocation from the same users as well as metrics as described in +go/chrome-infra-telemetry-readme. You may choose to opt out of this collection +at any time by setting the flag `enabled = False` under [trace] section in +{config_file} +or by executing from your depot_tools checkout: + +vpython3 third_party/depot_tools/infra_lib/telemetry --disable + +This notice will be displayed {run_count} more times. +=============================================================================== +""" + +# This does not include Googlers' physical machines/laptops +_GOOGLE_HOSTNAME_SUFFIX = ('.google.com', '.googler.com', '.googlers.com') + +# The version keeps track of telemetry changes. +_TELEMETRY_VERSION = '3' + + +def get_host_name(fully_qualified: bool = False) -> str: + """Return hostname of current machine, with domain if |fully_qualified|.""" + hostname = socket.gethostname() + try: + hostname = socket.gethostbyaddr(hostname)[0] + except (socket.gaierror, socket.herror) as e: + logging.warning( + 'please check your /etc/hosts file; resolving your hostname' + ' (%s) failed: %s', + hostname, + e, + ) + + if fully_qualified: + return hostname + return hostname.partition('.')[0] + + +def is_google_host() -> bool: + """Checks if the code is running on google host.""" + + hostname = get_host_name(fully_qualified=True) + return hostname.endswith(_GOOGLE_HOSTNAME_SUFFIX) + + +def initialize(service_name, + notice=DEFAULT_BANNER, + cfg_file=config.DEFAULT_CONFIG_FILE): + if not is_google_host(): + return + + # TODO(326277821): Add support for other platforms + if not sys.platform == 'linux': + return + + cfg = config.Config(cfg_file) + + if not cfg.trace_config.has_enabled(): + if cfg.root_config.notice_countdown > -1: + print(notice.format(run_count=cfg.root_config.notice_countdown, + config_file=cfg_file), + file=sys.stderr) + cfg.root_config.update( + notice_countdown=cfg.root_config.notice_countdown - 1) + else: + cfg.trace_config.update(enabled=True, reason='AUTO') + + cfg.flush() + + if not cfg.trace_config.enabled: + return + + default_resource = otel_resources.Resource.create({ + otel_resources.SERVICE_NAME: + service_name, + 'telemetry.version': + _TELEMETRY_VERSION, + }) + + detected_resource = otel_resources.get_aggregated_resources([ + otel_resources.ProcessResourceDetector(), + otel_resources.OTELResourceDetector(), + detector.ProcessDetector(), + detector.SystemDetector(), + ]) + + resource = detected_resource.merge(default_resource) + trace_provider = otel_trace_sdk.TracerProvider(resource=resource) + otel_trace_api.set_tracer_provider(trace_provider) + trace_provider.add_span_processor( + otel_export.BatchSpanProcessor( + # Replace with ConsoleSpanExporter() to debug spans on the console + clearcut_span_exporter.ClearcutSpanExporter())) + + +def get_tracer(name: str, version: Optional[str] = None): + return otel_trace_api.get_tracer(name, version) diff --git a/infra_lib/telemetry/__main__.py b/infra_lib/telemetry/__main__.py new file mode 100755 index 000000000..18c56ee32 --- /dev/null +++ b/infra_lib/telemetry/__main__.py @@ -0,0 +1,41 @@ +#!/bin/env vpython3 +# Copyright 2024 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Utility for opting in or out of metrics collection""" +import argparse +import sys +import pathlib + +import config + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + + parser.add_argument('--disable', + '-d', + dest='enable', + action='store_false', + default=None, + help='Disable telemetry collection.') + + parser.add_argument('--enable', + '-e', + dest='enable', + action='store_true', + default=None, + help='Enable telemetry collection.') + + args = parser.parse_args() + + if args.enable is not None: + cfg = config.Config(config.DEFAULT_CONFIG_FILE) + cfg.trace_config.update(args.enable, 'USER') + cfg.flush() + else: + print('Error: --enable or --disable flag is required.') + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/infra_lib/telemetry/config.py b/infra_lib/telemetry/config.py index 640921f5f..24b0a8639 100644 --- a/infra_lib/telemetry/config.py +++ b/infra_lib/telemetry/config.py @@ -13,6 +13,9 @@ import time from typing import Literal import uuid +DEFAULT_CONFIG_FILE = Path(Path.home(), + '.config/chrome_infra/telemetry/telemetry.cfg') + ROOT_SECTION_KEY = "root" NOTICE_COUNTDOWN_KEY = "notice_countdown" ENABLED_KEY = "enabled"